mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 06:43:24 +00:00
Compare commits
682 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 |
33
.babelrc
33
.babelrc
@@ -1,3 +1,34 @@
|
|||||||
{
|
{
|
||||||
"presets": ["env", "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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ root = true
|
|||||||
|
|
||||||
# change these settings to your own preference
|
# change these settings to your own preference
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
|
|
||||||
# we recommend you to keep these unchanged
|
# we recommend you to keep these unchanged
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
|
|||||||
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,
|
"jsx": true,
|
||||||
"classes": true,
|
"classes": true,
|
||||||
"modules": true
|
"modules": true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"node": true
|
"node": true,
|
||||||
|
"es6": true
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react"
|
"react"
|
||||||
@@ -33,7 +34,6 @@
|
|||||||
"ClassDeclaration": true
|
"ClassDeclaration": true
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
"no-console": 2,
|
|
||||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||||
"comma-style": [2, "last"],
|
"comma-style": [2, "last"],
|
||||||
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],
|
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,3 +7,6 @@ nginx.pid
|
|||||||
/bin
|
/bin
|
||||||
env
|
env
|
||||||
*.swp
|
*.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.8.1"
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
|
|
||||||
script:
|
|
||||||
- npm run lint
|
|
||||||
- npm test
|
|
||||||
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.
|
||||||
61
README.md
61
README.md
@@ -1,4 +1,4 @@
|
|||||||
 [](https://travis-ci.org/EDCD/coriolis) [](https://discord.gg/0uwCh6R62aPRjk9w)
|
[](https://discord.gg/0uwCh6R62aPRjk9w)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
@@ -8,50 +8,41 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
|
|||||||
|
|
||||||
## Contributing
|
## 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.
|
- [Submit issues](https://github.com/EDCD/coriolis/issues)
|
||||||
|
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
|
||||||
### Translations
|
- Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||||
|
|
||||||
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
|
|
||||||
These will be merged regularly by the project manager.
|
|
||||||
|
|
||||||
### Feature Requests, Suggestions & Bugs
|
|
||||||
|
|
||||||
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
|
||||||
|
|
||||||
## Development
|
## 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
|
### Ship and Module Database
|
||||||
|
|
||||||
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
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
|
```sh
|
||||||
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
> 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
|
After this you need to serve the files in some manner.
|
||||||
|
Either configure your webserver to make the actual `build/` directory
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
visible on the web, or alternatively copy it to somewhere to serve it
|
||||||
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
|
from.
|
||||||
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.
|
|
||||||
|
|||||||
@@ -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%2FPwMAAGvB0AkAAAA%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%2FA7kMAExxqlSAAAAA&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.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&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.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
|
||||||
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%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
|
||||||
const importData = require('./fixtures/companion-api-import-3');
|
|
||||||
pasteText(JSON.stringify(importData));
|
|
||||||
|
|
||||||
expect(modal.state.importValid).toBeTruthy();
|
|
||||||
expect(modal.state.errorMsg).toEqual(null);
|
|
||||||
expect(modal.state.singleBuild).toBe(true);
|
|
||||||
clickProceed();
|
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
|
||||||
const importData = require('./fixtures/companion-api-import-4');
|
|
||||||
pasteText(JSON.stringify(importData));
|
|
||||||
|
|
||||||
expect(modal.state.importValid).toBeTruthy();
|
|
||||||
expect(modal.state.errorMsg).toEqual(null);
|
|
||||||
expect(modal.state.singleBuild).toBe(true);
|
|
||||||
clickProceed();
|
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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';
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
36696
package-lock.json
generated
36696
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
154
package.json
154
package.json
@@ -1,116 +1,112 @@
|
|||||||
{
|
{
|
||||||
"name": "coriolis_shipyard",
|
"name": "coriolis_shipyard",
|
||||||
"version": "2.9.7",
|
"version": "3.0.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/EDCD/coriolis"
|
"url": "https://github.com/EDCD/coriolis"
|
||||||
},
|
},
|
||||||
"homepage": "https://coriolis.edcd.io",
|
"homepage": "https://coriolis.io",
|
||||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engine": "node >= 4.8.1",
|
"engine": "node >= 4.8.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"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",
|
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||||
"clean": "rimraf build",
|
"clean": "rimraf build",
|
||||||
"start": "node devServer.js",
|
"start": "node devServer.js",
|
||||||
"lint": "eslint --ext .js,.jsx src",
|
"lint": "eslint --ext .js,.jsx src",
|
||||||
"test": "jest",
|
|
||||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||||
"build": "npm run clean && cross-env 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",
|
"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"
|
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||||
},
|
},
|
||||||
"jest": {
|
|
||||||
"transform": {
|
|
||||||
".*": "<rootDir>/node_modules/babel-jest"
|
|
||||||
},
|
|
||||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
|
||||||
"moduleFileExtensions": [
|
|
||||||
"js",
|
|
||||||
"json",
|
|
||||||
"jsx"
|
|
||||||
],
|
|
||||||
"automock": true,
|
|
||||||
"bail": false,
|
|
||||||
"unmockedModulePathPatterns": [
|
|
||||||
"<rootDir>/node_modules/lodash",
|
|
||||||
"<rootDir>/node_modules/react",
|
|
||||||
"<rootDir>/node_modules/react-dom",
|
|
||||||
"<rootDir>/node_modules/react-transition-group",
|
|
||||||
"<rootDir>/node_modules/react-testutils-additions",
|
|
||||||
"<rootDir>/node_modules/fbjs",
|
|
||||||
"<rootDir>/node_modules/fbemitter",
|
|
||||||
"<rootDir>/node_modules/classnames",
|
|
||||||
"<rootDir>/node_modules/d3",
|
|
||||||
"<rootDir>/node_modules/lz-string",
|
|
||||||
"<rootDir>/node_modules/jsen",
|
|
||||||
"coriolis-data",
|
|
||||||
"<rootDir>/src/app/shipyard",
|
|
||||||
"<rootDir>/src/app/i18n",
|
|
||||||
"<rootDir>/src/app/utils",
|
|
||||||
"<rootDir>/src/schemas",
|
|
||||||
"<rootDir>/__tests__"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"appcache-webpack-plugin": "^1.3.0",
|
"@babel/core": "^7.0.0",
|
||||||
"babel-core": "*",
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
"babel-eslint": "*",
|
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||||
"babel-jest": "*",
|
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||||
"babel-loader": "*",
|
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||||
"babel-preset-env": "*",
|
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||||
"babel-preset-react": "*",
|
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||||
"babel-preset-stage-0": "*",
|
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||||
"create-react-class": "^15.6.2",
|
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||||
"css-loader": "^0.28.0",
|
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||||
"cross-env": "^5.1.4",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||||
"d3-selection": "1",
|
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||||
"eslint": "3.19.0",
|
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||||
"eslint-plugin-react": "^6.10.3",
|
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||||
"expose-loader": "^0.7.3",
|
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||||
"express": "^4.15.2",
|
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||||
"extract-text-webpack-plugin": "2.1.0",
|
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||||
"file-loader": "^0.11.1",
|
"@babel/preset-env": "^7.0.0",
|
||||||
"html-webpack-plugin": "^2.28.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"jest-cli": "^21.2.1",
|
"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",
|
"jsen": "^0.6.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"less": "^2.7.2",
|
"less": "^3.8.1",
|
||||||
"less-loader": "^4.0.3",
|
"less-loader": "^4.1.0",
|
||||||
"react-addons-perf": "^15.4.2",
|
"react-addons-perf": "^15.4.2",
|
||||||
"react-measure": "^1.4.7",
|
"react-container-dimensions": "^1.4.1",
|
||||||
"react-testutils-additions": "^15.2.0",
|
"react-testutils-additions": "^16.0.0",
|
||||||
"react-transition-group": "^1.1.2",
|
"react-transition-group": "^2.5.0",
|
||||||
"rimraf": "^2.6.1",
|
"rimraf": "^2.6.1",
|
||||||
"rollup": "0.41",
|
"rollup": "^0.66.2",
|
||||||
"rollup-plugin-node-resolve": "3",
|
"rollup-plugin-node-resolve": "^3.4.0",
|
||||||
"style-loader": "^0.16.1",
|
"style-loader": "^0.23.0",
|
||||||
"uglify-js": "^2.4.11",
|
"uglify-js": "^3.4.9",
|
||||||
"url-loader": "^0.5.8",
|
"url-loader": "^1.1.1",
|
||||||
"webpack": "^2.4.1",
|
"webpack": "^4.20.2",
|
||||||
"webpack-dev-server": "^2.4.4",
|
"webpack-bugsnag-plugins": "^1.2.2",
|
||||||
|
"webpack-cli": "^3.1.1",
|
||||||
|
"webpack-dev-server": "^3.1.9",
|
||||||
"webpack-notifier": "^1.6.0",
|
"webpack-notifier": "^1.6.0",
|
||||||
"webpack-bugsnag-plugins": "^1.1.1"
|
"workbox-webpack-plugin": "^3.6.1"
|
||||||
},
|
},
|
||||||
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-polyfill": "*",
|
"@babel/polyfill": "^7.0.0",
|
||||||
|
"auto-bind": "^2.1.1",
|
||||||
"browserify-zlib-next": "^1.0.1",
|
"browserify-zlib-next": "^1.0.1",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.6",
|
||||||
"coriolis-data": "../coriolis-data",
|
"d3": "^5.7.0",
|
||||||
"d3": "4.8.0",
|
"detect-browser": "^3.0.1",
|
||||||
"detect-browser": "^1.7.0",
|
"ed-forge": "../ed-forge",
|
||||||
"fbemitter": "^2.1.1",
|
"fbemitter": "^2.1.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.11",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"pako": "^1.0.6",
|
"pako": "^1.0.6",
|
||||||
"prop-types": "^15.5.8",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
"react-dom": "^15.5.4",
|
"react-dom": "^15.5.4",
|
||||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
"react-extras": "^0.7.1",
|
||||||
"recharts": "^0.22.3",
|
"react-fuzzy": "^0.5.2",
|
||||||
"superagent": "^3.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,33 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Router from './Router';
|
import Router from './Router';
|
||||||
|
import { register } from 'register-service-worker';
|
||||||
import { EventEmitter } from 'fbemitter';
|
import { EventEmitter } from 'fbemitter';
|
||||||
import { getLanguage } from './i18n/Language';
|
import { getLanguage } from './i18n/Language';
|
||||||
import Persist from './stores/Persist';
|
import Persist from './stores/Persist';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
|
||||||
|
import Announcement from './components/Announcement';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Tooltip from './components/Tooltip';
|
import Tooltip from './components/Tooltip';
|
||||||
import ModalExport from './components/ModalExport';
|
|
||||||
import ModalHelp from './components/ModalHelp';
|
import ModalHelp from './components/ModalHelp';
|
||||||
import ModalImport from './components/ModalImport';
|
import ModalImport from './components/ModalImport';
|
||||||
import ModalPermalink from './components/ModalPermalink';
|
import ModalPermalink from './components/ModalPermalink';
|
||||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
|
||||||
import * as JournalUtils from './utils/JournalUtils';
|
|
||||||
|
|
||||||
import AboutPage from './pages/AboutPage';
|
import AboutPage from './pages/AboutPage';
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
import OutfittingPage from './pages/OutfittingPage';
|
import OutfittingPage from './pages/OutfittingPage';
|
||||||
import ComparisonPage from './pages/ComparisonPage';
|
|
||||||
import ShipyardPage from './pages/ShipyardPage';
|
import ShipyardPage from './pages/ShipyardPage';
|
||||||
import ErrorDetails from './pages/ErrorDetails';
|
import ErrorDetails from './pages/ErrorDetails';
|
||||||
|
|
||||||
const zlib = require('pako');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Coriolis App
|
* Coriolis App
|
||||||
*/
|
*/
|
||||||
export default class Coriolis extends React.Component {
|
export default class Coriolis extends React.Component {
|
||||||
|
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
closeMenu: PropTypes.func.isRequired,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
hideModal: PropTypes.func.isRequired,
|
hideModal: PropTypes.func.isRequired,
|
||||||
@@ -60,26 +55,24 @@ export default class Coriolis extends React.Component {
|
|||||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
this._importBuild = this._importBuild.bind(this);
|
|
||||||
|
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
this.state = {
|
this.state = {
|
||||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||||
page: null,
|
page: null,
|
||||||
|
announcements: [],
|
||||||
language: getLanguage(Persist.getLangCode()),
|
language: getLanguage(Persist.getLangCode()),
|
||||||
route: {},
|
route: {},
|
||||||
sizeRatio: Persist.getSizeRatio()
|
sizeRatio: Persist.getSizeRatio()
|
||||||
};
|
};
|
||||||
|
// TODO: New mechanism for announcements
|
||||||
|
// this._getAnnouncements();
|
||||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||||
Router('/import?', (r) => this._importBuild(r));
|
Router('/import?', (r) => this._importBuild(r));
|
||||||
Router('/import/:data', (r) => this._importBuild(r));
|
Router('/import/:data', (r) => this._importBuild(r));
|
||||||
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
|
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
|
||||||
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
|
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
|
||||||
Router('/outfit/:ship/:code?', (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('/about', (r) => this._setPage(AboutPage, r));
|
||||||
Router('*', (r) => this._setPage(null, r));
|
Router('*', (r) => this._setPage(null, r));
|
||||||
}
|
}
|
||||||
@@ -91,24 +84,25 @@ export default class Coriolis extends React.Component {
|
|||||||
_importBuild(r) {
|
_importBuild(r) {
|
||||||
try {
|
try {
|
||||||
// Need to decode and gunzip the data, then build the ship
|
// Need to decode and gunzip the data, then build the ship
|
||||||
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
|
let ship = new Ship(r.params.data);
|
||||||
const json = JSON.parse(data);
|
r.params.ship = ship.getShipType();
|
||||||
console.log('Ship import data: ');
|
r.params.code = ship.compress();
|
||||||
console.log(json);
|
|
||||||
let ship;
|
|
||||||
if (json && json.modules) {
|
|
||||||
ship = CompanionApiUtils.shipFromJson(json);
|
|
||||||
} else if (json && json.Modules) {
|
|
||||||
ship = JournalUtils.shipFromLoadoutJSON(json);
|
|
||||||
}
|
|
||||||
r.params.ship = ship.id;
|
|
||||||
r.params.code = ship.toString();
|
|
||||||
this._setPage(OutfittingPage, r);
|
this._setPage(OutfittingPage, r);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._onError('Failed to import ship', r.path, 0, 0, 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
|
* Updates / Sets the page and route context
|
||||||
* @param {[type]} page The page to be shown
|
* @param {[type]} page The page to be shown
|
||||||
@@ -134,9 +128,9 @@ export default class Coriolis extends React.Component {
|
|||||||
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
||||||
if (errObj) {
|
if (errObj) {
|
||||||
if (errObj instanceof Error) {
|
if (errObj instanceof Error) {
|
||||||
bugsnagClient.notify(errObj) // eslint-disable-line
|
bugsnagClient.notify(errObj); // eslint-disable-line
|
||||||
} else if (errObj instanceof String) {
|
} else if (errObj instanceof String) {
|
||||||
bugsnagClient.notify(msg, errObj) // eslint-disable-line
|
bugsnagClient.notify(msg, errObj); // eslint-disable-line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -180,13 +174,13 @@ export default class Coriolis extends React.Component {
|
|||||||
case 72: // 'h'
|
case 72: // 'h'
|
||||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
|
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._showModal(<ModalHelp />);
|
this._showModal(<ModalHelp/>);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 73: // 'i'
|
case 73: // 'i'
|
||||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._showModal(<ModalImport />);
|
this._showModal(<ModalImport/>);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 79: // 'o'
|
case 79: // 'o'
|
||||||
@@ -208,7 +202,7 @@ export default class Coriolis extends React.Component {
|
|||||||
* @param {React.Component} content Modal Content
|
* @param {React.Component} content Modal Content
|
||||||
*/
|
*/
|
||||||
_showModal(content) {
|
_showModal(content) {
|
||||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
|
let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>;
|
||||||
this.setState({ modal });
|
this.setState({ modal });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,14 +316,50 @@ export default class Coriolis extends React.Component {
|
|||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
// Listen for appcache updated event, present refresh to update view
|
// Listen for appcache updated event, present refresh to update view
|
||||||
if (window.applicationCache) {
|
// Check that service workers are registered
|
||||||
window.applicationCache.addEventListener('updateready', () => {
|
if (navigator.storage && navigator.storage.persist) {
|
||||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
window.addEventListener('load', () => {
|
||||||
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
|
navigator.storage.persist().then(granted => {
|
||||||
|
if (granted)
|
||||||
|
console.log('Storage will not be cleared except by explicit user action');
|
||||||
|
else
|
||||||
|
console.log('Storage may be cleared by the UA under storage pressure.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
// 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.onerror = this._onError.bind(this);
|
||||||
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
|
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
|
||||||
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
|
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
|
||||||
@@ -346,15 +376,28 @@ export default class Coriolis extends React.Component {
|
|||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let currentMenu = this.state.currentMenu;
|
let currentMenu = this.state.currentMenu;
|
||||||
|
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
|
||||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
|
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/> }
|
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
||||||
{ this.state.modal }
|
currentMenu={currentMenu}/>
|
||||||
{ this.state.tooltip }
|
{/* <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>
|
<footer>
|
||||||
|
|
||||||
<div className="right cap">
|
<div className="right cap">
|
||||||
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
<a href="https://github.com/EDCD/coriolis" target="_blank" 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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>;
|
</div>;
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ Router.go = function(path, state) {
|
|||||||
gaTrack(path);
|
gaTrack(path);
|
||||||
let ctx = new Context(path, state);
|
let ctx = new Context(path, state);
|
||||||
Router.dispatch(ctx);
|
Router.dispatch(ctx);
|
||||||
|
|
||||||
if (!ctx.unhandled) {
|
if (!ctx.unhandled) {
|
||||||
if (isStandAlone()) {
|
if (isStandAlone()) {
|
||||||
Persist.setState(ctx);
|
Persist.setState(ctx);
|
||||||
@@ -257,9 +258,8 @@ Route.prototype.match = function(path, params) {
|
|||||||
* @param {string} path Path to track
|
* @param {string} path Path to track
|
||||||
*/
|
*/
|
||||||
function gaTrack(path) {
|
function gaTrack(path) {
|
||||||
if (window.ga) {
|
const _paq = window._paq || [];
|
||||||
window.ga('send', 'pageview', path);
|
_paq.push(['trackPageView']);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ function isActive(href) {
|
|||||||
* Active Link - Highlighted when URL matches window location
|
* Active Link - Highlighted when URL matches window location
|
||||||
*/
|
*/
|
||||||
export default class ActiveLink extends Link {
|
export default class ActiveLink extends Link {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the component
|
* Renders the component
|
||||||
* @return {React.Component} The active link
|
* @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>;
|
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,106 +1,34 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
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
|
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||||
|
|
||||||
/*
|
const MOUNT_MAP = {
|
||||||
* Categorisation of module groups
|
fixed: <MountFixed className={'lg'} />,
|
||||||
*/
|
gimbal: <MountGimballed className={'lg'} />,
|
||||||
const GRPCAT = {
|
turret: <MountTurret className={'lg'} />,
|
||||||
'sg': 'shields',
|
|
||||||
'bsg': 'shields',
|
|
||||||
'psg': 'shields',
|
|
||||||
'scb': 'shields',
|
|
||||||
'cc': 'limpet controllers',
|
|
||||||
'fx': 'limpet controllers',
|
|
||||||
'hb': 'limpet controllers',
|
|
||||||
'pc': 'limpet controllers',
|
|
||||||
'rpl': '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',
|
|
||||||
'axmc': 'experimental',
|
|
||||||
'fc': 'projectiles',
|
|
||||||
'rfl': 'experimental',
|
|
||||||
'pa': 'projectiles',
|
|
||||||
'rg': 'projectiles',
|
|
||||||
'mr': 'ordnance',
|
|
||||||
'axmr': 'experimental',
|
|
||||||
'tp': 'ordnance',
|
|
||||||
'nl': 'ordnance',
|
|
||||||
'sc': 'scanners',
|
|
||||||
'ss': 'scanners',
|
|
||||||
// Utilities
|
|
||||||
'cs': 'scanners',
|
|
||||||
'kw': 'scanners',
|
|
||||||
'ws': 'scanners',
|
|
||||||
'xs': 'scanners',
|
|
||||||
'ch': 'defence',
|
|
||||||
'po': 'defence',
|
|
||||||
'ec': 'defence',
|
|
||||||
'sfn': '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', 'rpl'],
|
|
||||||
'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
|
|
||||||
// Experimental
|
|
||||||
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn']
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Available modules menu
|
* Available modules menu
|
||||||
*/
|
*/
|
||||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
diffDetails: PropTypes.func,
|
hideSearch: PropTypes.bool,
|
||||||
m: PropTypes.object,
|
m: PropTypes.object,
|
||||||
shipMass: PropTypes.number,
|
warning: PropTypes.func,
|
||||||
warning: PropTypes.func
|
slotDiv: PropTypes.object
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
shipMass: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,7 +38,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
this._hideDiff = this._hideDiff.bind(this);
|
autoBind(this);
|
||||||
this.state = this._initState(props, context);
|
this.state = this._initState(props, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,175 +49,230 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
* @return {Object} list: Array of React Components, currentGroup Component if any
|
* @return {Object} list: Array of React Components, currentGroup Component if any
|
||||||
*/
|
*/
|
||||||
_initState(props, context) {
|
_initState(props, context) {
|
||||||
let translate = context.language.translate;
|
const { translate } = context.language;
|
||||||
let { m, warning, shipMass, onSelect, modules } = props;
|
const { m } = props;
|
||||||
let list, currentGroup;
|
const list = [], fuzzy = [];
|
||||||
let buildGroup = this._buildGroup.bind(
|
let currentGroup;
|
||||||
this,
|
|
||||||
translate,
|
const modules = m.getApplicableItems().map(getModuleInfo);
|
||||||
m,
|
const groups = mapValues(
|
||||||
warning,
|
groupBy(modules, (info) => info.meta.group),
|
||||||
shipMass - (m && m.mass ? m.mass : 0),
|
(infos) => groupBy(infos, (info) => info.meta.type),
|
||||||
(m, event) => {
|
|
||||||
this._hideDiff(event);
|
|
||||||
onSelect(m);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
// Build categories sorted by translated category name
|
||||||
if (modules instanceof Array) {
|
const groupKeys = sortBy(Object.keys(groups), translate);
|
||||||
list = buildGroup(modules[0].grp, modules);
|
for (const group of groupKeys) {
|
||||||
} else {
|
const groupName = translate(group);
|
||||||
list = [];
|
if (groupKeys.length > 1) {
|
||||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
list.push(<div key={`group-${group}`} className="select-category upp">{groupName}</div>);
|
||||||
if (m) {
|
|
||||||
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to regroup the modules by our own categorisation
|
const categories = groups[group];
|
||||||
let catmodules = {};
|
const categoryKeys = sortBy(Object.keys(categories), translate);
|
||||||
// Pre-create to preserve ordering
|
for (const category of categoryKeys) {
|
||||||
for (let cat in CATEGORIES) {
|
const categoryName = translate(category);
|
||||||
catmodules[cat] = [];
|
const infos = categories[category];
|
||||||
|
if (categoryKeys.length > 1) {
|
||||||
|
list.push(<div key={`category-${category}`} className="select-group cap">{categoryName}</div>);
|
||||||
}
|
}
|
||||||
for (let g in modules) {
|
list.push(
|
||||||
const moduleCategory = GRPCAT[g] || g;
|
this._buildGroup(
|
||||||
const existing = catmodules[moduleCategory] || [];
|
m,
|
||||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
category,
|
||||||
}
|
infos,
|
||||||
|
),
|
||||||
for (let category in catmodules) {
|
);
|
||||||
let categoryHeader = false;
|
fuzzy.push(
|
||||||
// Order through CATEGORIES if present
|
...infos.map((info) => {
|
||||||
const categories = CATEGORIES[category] || [category];
|
const { meta } = info;
|
||||||
if (categories && categories.length) {
|
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
|
||||||
for (let n in categories) {
|
return {
|
||||||
const grp = categories[n];
|
grp: groupName,
|
||||||
// We now have the group and the category. We might not have any modules, though...
|
cat: categoryName,
|
||||||
if (modules[grp]) {
|
m: info.proto.Item,
|
||||||
// Decide if we need a category header as well as a group header
|
name: `${meta.class}${meta.rating}${mount} ${categoryName}`,
|
||||||
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]));
|
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { list, currentGroup };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate React Components for Module Group
|
* Generate React Components for Module Group
|
||||||
* @param {Function} translate Translate function
|
* @param {Object} mountedModule Mounted Module
|
||||||
* @param {Objecy} mountedModule Mounted Module
|
* @param {String} category Category key
|
||||||
* @param {Funciton} warningFunc Warning function
|
|
||||||
* @param {number} mass Mass
|
|
||||||
* @param {function} onSelect Select/Mount callback
|
|
||||||
* @param {string} grp Group name
|
|
||||||
* @param {Array} modules Available modules
|
* @param {Array} modules Available modules
|
||||||
* @return {React.Component} Available Module Group contents
|
* @return {React.Component} Available Module Group contents
|
||||||
*/
|
*/
|
||||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
|
_buildGroup(mountedModule, category, modules) {
|
||||||
let prevClass = null, prevRating = null;
|
const { warning } = this.props;
|
||||||
let elems = [];
|
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
|
// Reverse sort for descending order of module class
|
||||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
|
for (const clazz of Object.keys(classMapping).sort().reverse()) {
|
||||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
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
|
||||||
for (let i = 0; i < sortedModules.length; i++) {
|
let disabled = info.props.shieldgenmaximalmass < ship.readProp('hullmass');
|
||||||
let m = sortedModules[i];
|
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||||
let mount = null;
|
disabled =
|
||||||
let disabled = false;
|
4 <=
|
||||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||||
// Shield generators care about maximum hull mass
|
.length;
|
||||||
disabled = mass > m.maxmass;
|
|
||||||
} else if (m.maxmass) {
|
|
||||||
// Thrusters care about total mass
|
|
||||||
disabled = mass + m.mass > m.maxmass;
|
|
||||||
}
|
}
|
||||||
let active = mountedModule && mountedModule.id === m.id;
|
|
||||||
let classes = cn(m.name ? 'lc' : 'c', {
|
|
||||||
warning: !disabled && warningFunc && warningFunc(m),
|
|
||||||
active,
|
|
||||||
disabled
|
|
||||||
});
|
|
||||||
let eventHandlers;
|
|
||||||
|
|
||||||
if (disabled || active) {
|
// Default event handlers for objects that are disabled
|
||||||
eventHandlers = {};
|
let eventHandlers = {};
|
||||||
} else {
|
if (!disabled) {
|
||||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
const showDiff = this._showDiff.bind(this, mountedModule, info);
|
||||||
let select = onSelect.bind(null, m);
|
const select = (event) => {
|
||||||
|
this._hideDiff(event);
|
||||||
|
this.props.onSelect(Item);
|
||||||
|
};
|
||||||
|
|
||||||
eventHandlers = {
|
eventHandlers = {
|
||||||
onMouseEnter: this._over.bind(this, showDiff),
|
onMouseEnter: this._over.bind(this, showDiff),
|
||||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||||
onTouchEnd: this._touchEnd.bind(this, select),
|
onTouchEnd: this._touchEnd.bind(this, select),
|
||||||
onMouseLeave: this._hideDiff,
|
onMouseLeave: this._hideDiff,
|
||||||
onClick: select
|
onClick: select,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(m.mount) {
|
const mountSymbol = MOUNT_MAP[meta.mount];
|
||||||
case 'F': mount = <MountFixed className={'lg'} />; break;
|
const li = (
|
||||||
case 'G': mount = <MountGimballed className={'lg'}/>; break;
|
<li key={Item} data-id={Item}
|
||||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
|
||||||
}
|
className={cn(meta.type === 'armour' ? 'lc' : 'c', {
|
||||||
|
warning: !disabled && warning && warning(info),
|
||||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
active: mountedModule.getItem() === Item,
|
||||||
elems.push(<br key={'b' + m.grp + i} />);
|
disabled,
|
||||||
itemsOnThisRow = 0;
|
hardpoint: mountSymbol,
|
||||||
}
|
})}
|
||||||
|
{...eventHandlers}
|
||||||
elems.push(
|
>{mountSymbol}{meta.type === 'armour' ? Item : `${meta.class}${meta.rating}`}</li>
|
||||||
<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;
|
const tail = elems.pop();
|
||||||
prevRating = m.rating;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ul key={'modules' + grp} >{elems}</ul>;
|
return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
* @param {Object} mm The module mounet currently
|
* @param {Object} mountedModule The module mounted currently
|
||||||
* @param {Object} m The hovered module
|
* @param {Object} hoveringModule The hovered module
|
||||||
* @param {DOMRect} rect DOMRect for target element
|
* @param {DOMRect} rect DOMRect for target element
|
||||||
*/
|
*/
|
||||||
_showDiff(mm, m, rect) {
|
_showDiff(mountedModule, hoveringModule, rect) {
|
||||||
if (this.props.diffDetails) {
|
const { tooltip, language } = this.context;
|
||||||
|
const { formats, translate, units } = language;
|
||||||
this.touchTimeout = null;
|
this.touchTimeout = null;
|
||||||
this.context.tooltip(this.props.diffDetails(m, mm), rect);
|
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>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,52 +319,22 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this.context.tooltip();
|
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
|
* Scroll to mounted (if it exists) module group on mount
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.groupElem) { // Scroll to currently selected group
|
if (this.activeSlotRef) {
|
||||||
this.node.scrollTop = this.groupElem.offsetTop;
|
this.activeSlotRef.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle focus if the component updates
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.props.slotDiv) {
|
||||||
|
this.props.slotDiv.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,12 +356,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
<div ref={node => this.node = node}
|
<div ref={node => this.node = node}
|
||||||
className={cn('select', this.props.className)}
|
className={cn('select', this.props.className)}
|
||||||
onScroll={this._hideDiff}
|
onScroll={this._hideDiff}
|
||||||
onClick={(e) => e.stopPropagation() }
|
onClick={(e) => e.stopPropagation()}
|
||||||
onContextMenu={stopCtxPropagation}
|
onContextMenu={stopCtxPropagation}
|
||||||
>
|
>
|
||||||
|
{this._showSearch()}
|
||||||
{this.state.list}
|
{this.state.list}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
|
|||||||
* Bar Chart
|
* Bar Chart
|
||||||
*/
|
*/
|
||||||
export default class BarChart extends TranslatedComponent {
|
export default class BarChart extends TranslatedComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
||||||
labels: null,
|
labels: null,
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
import autoBind from 'auto-bind';
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Pip } from './SvgIcons';
|
|
||||||
import LineChart from '../components/LineChart';
|
|
||||||
import Slider from '../components/Slider';
|
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boost displays a boost button that toggles bosot
|
* Boost displays a boost button that toggles bosot
|
||||||
@@ -15,8 +9,6 @@ import Module from '../shipyard/Module';
|
|||||||
*/
|
*/
|
||||||
export default class Boost extends TranslatedComponent {
|
export default class Boost extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -26,12 +18,9 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
* @param {Object} context React Component context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { ship, boost } = props;
|
autoBind(this);
|
||||||
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._toggleBoost = this._toggleBoost.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,13 +66,12 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { translate } = this.context.language;
|
||||||
const { ship, boost } = this.props;
|
|
||||||
|
|
||||||
// TODO disable if ship cannot boost
|
|
||||||
return (
|
return (
|
||||||
<span id='boost'>
|
<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>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cargo slider
|
* Cargo slider
|
||||||
@@ -22,8 +22,7 @@ export default class Cargo extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._cargoChange = this._cargoChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
|
|||||||
* Comparison Table
|
* Comparison Table
|
||||||
*/
|
*/
|
||||||
export default class ComparisonTable extends TranslatedComponent {
|
export default class ComparisonTable extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
facets: PropTypes.array.isRequired,
|
facets: PropTypes.array.isRequired,
|
||||||
builds: PropTypes.array.isRequired,
|
builds: PropTypes.array.isRequired,
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory, Ship } from 'ed-forge';
|
||||||
import { Insurance } from '../shipyard/Constants';
|
import { Insurance } from '../shipyard/Constants';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
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
|
* Cost Section
|
||||||
*/
|
*/
|
||||||
export default class CostSection extends TranslatedComponent {
|
export default class CostSection extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
buildName: PropTypes.string
|
buildName: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,71 +26,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._costsTab = this._costsTab.bind(this);
|
autoBind(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);
|
|
||||||
|
|
||||||
|
const { ship, buildName } = props;
|
||||||
|
this.shipType = ship.getShipType();
|
||||||
this.state = {
|
this.state = {
|
||||||
retrofitShip,
|
retrofitName: Persist.hasBuild(ship.getShipType(), buildName) ? buildName : null,
|
||||||
retrofitName,
|
shipDiscount: Persist.getShipDiscount(),
|
||||||
shipDiscount,
|
moduleDiscount: Persist.getModuleDiscount(),
|
||||||
moduleDiscount,
|
|
||||||
insurance: Insurance[Persist.getInsurance()],
|
insurance: Insurance[Persist.getInsurance()],
|
||||||
tab: Persist.getCostTab(),
|
tab: Persist.getCostTab(),
|
||||||
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
buildOptions: Persist.getBuildsNamesFor(ship.getShipType()),
|
||||||
ammoPredicate: 'cr',
|
predicate: 'cr',
|
||||||
ammoDesc: true,
|
desc: true,
|
||||||
costPredicate: 'cr',
|
excluded: {},
|
||||||
costDesc: true,
|
|
||||||
retroPredicate: 'cr',
|
|
||||||
retroDesc: true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ship instance to base/reference retrofit changes from
|
* 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
|
* @return {Ship} Retrofit ship
|
||||||
*/
|
*/
|
||||||
_buildRetrofitShip(shipId, name, retrofitShip) {
|
_buildRetrofitShip() {
|
||||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
const { retrofitName } = this.state;
|
||||||
|
if (Persist.hasBuild(this.shipType, retrofitName)) {
|
||||||
if (!retrofitShip) { // Don't create a new instance unless needed
|
return new Ship(Persist.getBuild(this.shipType, retrofitName));
|
||||||
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
|
|
||||||
} else {
|
} 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() {
|
_onDiscountChanged() {
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
let shipDiscount = Persist.getShipDiscount();
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
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 });
|
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
|
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
||||||
*/
|
*/
|
||||||
_onBaseRetrofitChange(event) {
|
_onBaseRetrofitChange(event) {
|
||||||
let retrofitName = event.target.value;
|
this.setState({ 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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On builds changed check to see if the retrofit ship needs
|
* Toggle item cost inclusion
|
||||||
* to be updated
|
* @param {String} key Key of the row to toggle
|
||||||
*/
|
*/
|
||||||
_onBuildsChanged() {
|
_toggleExcluded(key) {
|
||||||
let update = false;
|
let { excluded } = this.state;
|
||||||
let ship = this.props.ship;
|
excluded = assign({}, excluded);
|
||||||
let { retrofitName, retrofitShip } = this.state;
|
const slotExcluded = excluded[key];
|
||||||
|
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
|
||||||
if(!Persist.hasBuild(ship.id, retrofitName)) {
|
this.setState({ excluded });
|
||||||
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) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle item cost inclusion in overall total
|
* Set list sort predicate
|
||||||
* @param {Object} item Cost item
|
* @param {string} newPredicate sort predicate
|
||||||
*/
|
*/
|
||||||
_toggleCost(item) {
|
_sortBy(newPredicate) {
|
||||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
let { predicate, desc } = this.state;
|
||||||
this.forceUpdate();
|
|
||||||
|
if (newPredicate == predicate) {
|
||||||
|
desc = !desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
this.setState({ predicate: newPredicate, desc });
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,18 +122,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
_costsTab() {
|
_costsTab() {
|
||||||
let { ship } = this.props;
|
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 { translate, formats, units } = this.context.language;
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.costList.length; i < l; i++) {
|
let modules = sortBy(
|
||||||
let item = ship.costList[i];
|
ship.getModules(),
|
||||||
if (item.m && item.m.cost) {
|
(predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
|
||||||
let toggle = this._toggleCost.bind(this, item);
|
);
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
|
if (desc) {
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
|
reverse(modules);
|
||||||
<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 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>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,23 +158,23 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<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')}
|
{translate('module')}
|
||||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</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(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
<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>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
<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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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
|
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||||
*/
|
*/
|
||||||
_eddbShoppingList() {
|
_eddbShoppingList() {
|
||||||
const { retrofitCosts } = this.state;
|
const {} = this.state;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
|
|
||||||
// Provide unique list of non-PP module EDDB IDs to buy
|
// 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
|
// 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
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_retrofitTab() {
|
_retrofitTab() {
|
||||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
let { buildOptions, excluded, moduleDiscount, retrofitName } = this.state;
|
||||||
const { termtip, tooltip } = this.context;
|
const { termtip, tooltip } = this.context;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
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>
|
return <div>
|
||||||
<div className='scroll-x'>
|
<div className='scroll-x'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<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._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
|
||||||
{translate('net cost')}
|
{translate('net cost')}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<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'>
|
<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 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' }}>
|
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||||
{int(retrofitTotal)}{units.CR}
|
{int(retrofitTotal)}{units.CR}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<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 cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||||
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
<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>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -409,63 +304,50 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update retrofit costs
|
*
|
||||||
* @param {Ship} ship Ship instance
|
* @param {*} modules
|
||||||
* @param {Ship} retrofitShip Retrofit Ship instance
|
|
||||||
*/
|
*/
|
||||||
_updateRetrofit(ship, retrofitShip) {
|
_ammoInfo() {
|
||||||
let retrofitCosts = [];
|
const { ship } = this.props;
|
||||||
let retrofitTotal = 0, i, l, item;
|
const { desc, predicate } = this.state;
|
||||||
|
|
||||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
let info = [{
|
||||||
item = {
|
key: 'fuel',
|
||||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
item: 'Fuel',
|
||||||
buyId: ship.bulkheads.m.eddbID,
|
qty: ship.get(FUEL_CAPACITY),
|
||||||
buyPp: ship.bulkheads.m.pp,
|
unitCost: 50,
|
||||||
buyName: ship.bulkheads.m.name,
|
cost: 50 * ship.get(FUEL_CAPACITY),
|
||||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
}];
|
||||||
sellName: retrofitShip.bulkheads.m.name,
|
for (let m of ship.getModules()) {
|
||||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
const rebuilds = m.get('bays') * m.get('rebuildsperbay');
|
||||||
retroItem: retrofitShip.bulkheads
|
const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
|
||||||
};
|
if (ammo) {
|
||||||
retrofitCosts.push(item);
|
const unitCost = m.readMeta('ammocost');
|
||||||
if (retrofitShip.bulkheads.incCost) {
|
info.push({
|
||||||
retrofitTotal += item.netCost;
|
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 _sortF = undefined;
|
||||||
let retroSlotGroup = retrofitShip[g];
|
switch (predicate) {
|
||||||
let slotGroup = ship[g];
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
case 'qty': _sortF = (o) => o.qty; break;
|
||||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
case 'cost': _sortF = (o) => o.unitCost; break;
|
||||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
case 'm':
|
||||||
if (modId != retroModId) {
|
default: _sortF = (o) => o.item;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
info = sortBy(info, _sortF);
|
||||||
|
if (desc) {
|
||||||
|
reverse(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ retrofitCosts, retrofitTotal });
|
return info;
|
||||||
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -473,20 +355,24 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_ammoTab() {
|
_ammoTab() {
|
||||||
let { ammoTotal, ammoCosts } = this.state;
|
const { excluded } = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
const { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
const int = formats.int;
|
||||||
let rows = [];
|
const rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ammoCosts.length; i < l; i++) {
|
const ammoInfo = this._ammoInfo();
|
||||||
let item = ammoCosts[i];
|
let total = 0;
|
||||||
rows.push(<tr key={i} className='highlight'>
|
for (let i of ammoInfo) {
|
||||||
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td>
|
const disabled = excluded[i.key];
|
||||||
<td className='le shorten cap'>{slotName(translate, item)}</td>
|
rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
|
||||||
<td className='ri'>{int(item.max)}</td>
|
className={cn('highlight', { disabled })}>
|
||||||
<td className='ri'>{int(item.cost)}{units.CR}</td>
|
<td style={{ width: '1em' }}>{i.rating}</td>
|
||||||
<td className='ri'>{int(item.total)}{units.CR}</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>);
|
</tr>);
|
||||||
|
total += disabled ? 0 : i.cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
@@ -494,17 +380,17 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('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._sortBy('qty')}>{translate('qty')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
|
||||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
<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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -512,103 +398,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</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
|
* Add listeners on mount and update costs
|
||||||
*/
|
*/
|
||||||
@@ -616,64 +405,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
this.listeners = [
|
this.listeners = [
|
||||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||||
Persist.addListener('insurance', this._onInsuranceChanged.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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import VerticalBarChart from './VerticalBarChart';
|
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
|
* Defence information
|
||||||
@@ -15,12 +16,10 @@ import VerticalBarChart from './VerticalBarChart';
|
|||||||
*/
|
*/
|
||||||
export default class Defence extends TranslatedComponent {
|
export default class Defence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.object.isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
sys: PropTypes.number.isRequired,
|
|
||||||
opponentWep: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,22 +28,7 @@ export default class Defence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,175 +36,104 @@ export default class Defence extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, sys, opponentWep } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
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 = [];
|
// Data for pie chart (absolute MJ)
|
||||||
const effectiveShieldData = [];
|
const shieldSourcesData = [
|
||||||
const shieldDamageTakenData = [];
|
'byBoosters', 'byGenerator', 'byReinforcements', 'bySCBs',
|
||||||
const shieldSourcesTt = [];
|
].map((key) => { return { label: key, value: Math.round(shields[key]) }; });
|
||||||
const shieldDamageTakenAbsoluteTt = [];
|
|
||||||
const shieldDamageTakenExplosiveTt = [];
|
|
||||||
const shieldDamageTakenKineticTt = [];
|
|
||||||
const shieldDamageTakenThermalTt = [];
|
|
||||||
const effectiveShieldAbsoluteTt = [];
|
|
||||||
const effectiveShieldExplosiveTt = [];
|
|
||||||
const effectiveShieldKineticTt = [];
|
|
||||||
const effectiveShieldThermalTt = [];
|
|
||||||
let maxEffectiveShield = 0;
|
|
||||||
if (shield.total) {
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
|
||||||
|
|
||||||
if (shield.generator > 0) {
|
// Data for tooltip
|
||||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
const shieldSourcesTt = shieldSourcesData.map((o) => {
|
||||||
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
let { label, value } = o;
|
||||||
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
return <div key={label}>
|
||||||
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
{translate(label)} {formats.int(value)}{units.MJ}
|
||||||
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
</div>;
|
||||||
if (shield.boosters > 0) {
|
});
|
||||||
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shield.cells > 0) {
|
// Shield resistances
|
||||||
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const shieldDamageTakenData = [
|
||||||
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal',
|
||||||
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const dmgMult = shields[label];
|
||||||
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
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
|
// Effective MJ
|
||||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
const effectiveShieldData = [
|
||||||
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
|
const dmgMult = shields[label];
|
||||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
const raw = shields.withSCBs;
|
||||||
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
(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
|
const armour = ship.get(ARMOUR_METRICS);
|
||||||
if (shield.absolute.sys != 1) {
|
const moduleProtection = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
|
|
||||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
|
|
||||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
// Data for pie chart (absolute HP)
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
const armourSourcesData = ['base', 'byAlloys', 'byHRPs',].map(
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
(key) => { return { label: key, value: Math.round(armour[key]) }; }
|
||||||
|
);
|
||||||
|
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
// Data for tooltip
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
const armourSourcesTt = armourSourcesData.map((o) => {
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
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>);
|
// Armour resistances
|
||||||
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
const armourDamageTakenData = [
|
||||||
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
'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>);
|
// Effective HP
|
||||||
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
const effectiveArmourData = [
|
||||||
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
|
].map((label) => {
|
||||||
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
const dmgMult = armour[label];
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
|
const raw = armour.armour;
|
||||||
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
|
(label) => <div key={label}>
|
||||||
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
{translate(label)} {formats.int(raw * dmgMult[label])}
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
|
</div>
|
||||||
const effectiveThermalShield = shield.total / shield.thermal.total;
|
);
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
|
return { label, value: Math.round(dmgMult.damageMultiplier * raw), tooltip };
|
||||||
|
});
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
|
|
||||||
|
|
||||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
const armourSourcesData = [];
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
|
||||||
|
|
||||||
const armourSourcesTt = [];
|
|
||||||
const effectiveArmourAbsoluteTt = [];
|
|
||||||
const effectiveArmourExplosiveTt = [];
|
|
||||||
const effectiveArmourKineticTt = [];
|
|
||||||
const effectiveArmourThermalTt = [];
|
|
||||||
if (armour.bulkheads > 0) {
|
|
||||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
if (armour.reinforcement > 0) {
|
|
||||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawArmour = armour.bulkheads + armour.reinforcement;
|
|
||||||
|
|
||||||
const armourDamageTakenTt = [];
|
|
||||||
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
|
||||||
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenExplosiveTt = [];
|
|
||||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
|
||||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
|
||||||
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenKineticTt = [];
|
|
||||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
|
||||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
|
||||||
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenThermalTt = [];
|
|
||||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
|
||||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
|
||||||
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const effectiveArmourData = [];
|
|
||||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
|
|
||||||
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
|
|
||||||
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
|
||||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
|
|
||||||
|
|
||||||
const armourDamageTakenData = [];
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='defence'>
|
<span id='defence'>
|
||||||
{shield.total ? <span>
|
{shields.withSCBs ? <span>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('shield metrics')}</h2>
|
<h2>{translate('shield metrics')}</h2>
|
||||||
<br/>
|
<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, <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/>{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('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/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</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/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</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>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||||
@@ -238,11 +151,11 @@ export default class Defence extends TranslatedComponent {
|
|||||||
|
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('armour metrics')}</h2>
|
<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, <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/>{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_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(armour.modulearmour)}</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(armour.moduleprotection / 2)}</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(armour.moduleprotection)}</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/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import { moduleReduce } from 'ed-forge/lib/src/helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engagement range slider
|
* Engagement range slider
|
||||||
@@ -22,35 +22,18 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { ship } = props;
|
|
||||||
|
|
||||||
const maxRange = Math.round(this._calcMaxRange(ship));
|
|
||||||
|
|
||||||
this.state = {
|
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
|
* Update range
|
||||||
* @param {number} rangeLevel percentage level from 0 to 1
|
* @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);
|
const range = Math.round(rangeLevel * maxRange);
|
||||||
|
|
||||||
if (range !== this.props.engagementRange) {
|
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
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language, onWindowResize, sizeRatio } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate } = language;
|
||||||
const { engagementRange } = this.props;
|
const { engagementRange } = this.props;
|
||||||
const { maxRange } = this.state;
|
const { maxRange } = this.state;
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +1,59 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
import { getBoostMultiplier, getSpeedMultipliers } from 'ed-forge/lib/src/stats/SpeedProfile';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import { ShipProps } from 'ed-forge';
|
||||||
import Module from '../shipyard/Module';
|
const { LADEN_MASS } = ShipProps;
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engine profile for a given ship
|
* Engine profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class EngineProfile extends TranslatedComponent {
|
export default class EngineProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
fuel: PropTypes.number.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.number.isRequired,
|
||||||
boost: PropTypes.bool.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
|
* Render engine profile
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, eng, fuel, boost } = this.props;
|
const { code, ship, pips, boost } = this.props;
|
||||||
|
|
||||||
// Calculate bounds for our line chart
|
// Calculate bounds for our line chart
|
||||||
const thrusters = ship.standard[1].m;
|
const minMass = ship.readProp('hullmass');
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||||
const maxMass = thrusters.getMaxMass();
|
const baseSpeed = ship.readProp('speed');
|
||||||
const mass = ship.unladenMass + fuel + cargo;
|
const baseBoost = getBoostMultiplier(ship);
|
||||||
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
const cb = (eng, boost, mass) => {
|
||||||
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
const mult = getSpeedMultipliers(ship, mass)[(boost ? 4 : eng) / 0.5];
|
||||||
// Add a mark at our current mass
|
return baseSpeed * (boost ? baseBoost : 1) * mult;
|
||||||
const mark = Math.min(mass, maxMass);
|
};
|
||||||
|
|
||||||
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
|
||||||
|
|
||||||
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||||
return (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minSpeed}
|
yMin={cb(0, false, maxMass)}
|
||||||
yMax={maxSpeed}
|
yMax={cb(4, true, minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(ship.get(LADEN_MASS), maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum speed')}
|
yLabel={translate('maximum speed')}
|
||||||
yUnit={translate('m/s')}
|
yUnit={translate('m/s')}
|
||||||
func={this.state.calcMaxSpeedFunc}
|
func={cb.bind(this, pips.Eng.base + pips.Eng.mc, boost)}
|
||||||
points={1000}
|
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}
|
aspect={0.7}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,100 +1,48 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
import { calculateJumpRange } from 'ed-forge/lib/src/stats/JumpRangeProfile';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import { ShipProps } from 'ed-forge';
|
||||||
import Module from '../shipyard/Module';
|
const { LADEN_MASS } = ShipProps;
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FSD profile for a given ship
|
* FSD profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class FSDProfile extends TranslatedComponent {
|
export default class FSDProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: 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
|
* Render FSD profile
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, fuel } = this.props;
|
const { code, ship } = 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 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 (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minRange}
|
yMin={0}
|
||||||
yMax={maxRange}
|
yMax={cb(minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(mass, maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum range')}
|
yLabel={translate('maximum range')}
|
||||||
yUnit={translate('LY')}
|
yUnit={translate('LY')}
|
||||||
func={this.state.calcMaxRangeFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
aspect={0.7}
|
aspect={0.7}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fuel slider
|
* Fuel slider
|
||||||
@@ -22,8 +22,7 @@ export default class Fuel extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._fuelChange = this._fuelChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
|
|
||||||
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 {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
|
|
||||||
<div className={'cb'}>
|
|
||||||
<div className={'l'}>
|
|
||||||
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
|
|
||||||
{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,52 +1,58 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import cn from 'classnames';
|
|
||||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
|
const SIZE_ORDER = ['huge', 'large', 'medium', 'small'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hardpoint slot section
|
* Hardpoint slot section
|
||||||
*/
|
*/
|
||||||
export default class HardpointSlotSection extends SlotSection {
|
export default class HardpointSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'hardpoints', 'hardpoints');
|
super(props, 'hardpoints');
|
||||||
|
autoBind(this);
|
||||||
this._empty = this._empty.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all slots
|
* Empty all slots
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.props.ship.emptyWeapons();
|
this.props.ship.getHardpoints(undefined, true).forEach((slot) => slot.reset());
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill slots with specified module
|
* Fill slots with specified module
|
||||||
* @param {string} group Group name
|
* @param {string} type Type of item
|
||||||
* @param {string} mount Mount Type - F, G, T
|
* @param {string} rating Mount Type - (fixed, gimbal, turret)
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fill(group, mount, event) {
|
_fill(type, rating, event) {
|
||||||
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
const fillAll = event.getModifierState('Alt');
|
||||||
this.props.onChange();
|
this.props.ship.getHardpoints(undefined, true).forEach((slot) => {
|
||||||
this._close();
|
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;
|
||||||
* Empty all on section header right click
|
}
|
||||||
*/
|
}
|
||||||
_contextMenu() {
|
});
|
||||||
this._empty();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,33 +60,24 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getHardpoints(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
currentMenu={currentMenu}
|
||||||
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)}
|
drag={this._drag.bind(this, h)}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
ship={ship}
|
m={h}
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
enabled={h.enabled ? true : false}
|
||||||
|
propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -90,59 +87,68 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _fill = this._fill;
|
let _fill = this._fill;
|
||||||
|
|
||||||
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
<li className='lc' tabIndex="0" onClick={this._empty}>{translate('empty all')}</li>
|
||||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('pl')}</div>
|
<div className='select-group cap'>{translate('pulselaser')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('ul')}</div>
|
<div className='select-group cap'>{translate('burstlaser')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('bl')}</div>
|
<div className='select-group cap'>{translate('beamlaser')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('mc')}</div>
|
<div className='select-group cap'>{translate('multicannon')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('c')}</div>
|
<div className='select-group cap'>{translate('cannon')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('fc')}</div>
|
<div className='select-group cap'>{translate('fragcannon')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
|
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'gimbal')}><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', 'turret')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('pa')}</div>
|
<div className='select-group cap'>{translate('plasmaacc')}</div>
|
||||||
<ul>
|
<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>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('nl')}</div>
|
<div className='select-group cap'>{translate('railgun')}</div>
|
||||||
<ul>
|
<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>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import Link from './Link';
|
|||||||
import ActiveLink from './ActiveLink';
|
import ActiveLink from './ActiveLink';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
|
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import { toDetailedExport } from '../shipyard/Serializer';
|
|
||||||
import ModalDeleteAll from './ModalDeleteAll';
|
import ModalDeleteAll from './ModalDeleteAll';
|
||||||
import ModalExport from './ModalExport';
|
import ModalExport from './ModalExport';
|
||||||
import ModalHelp from './ModalHelp';
|
import ModalHelp from './ModalHelp';
|
||||||
import ModalImport from './ModalImport';
|
import ModalImport from './ModalImport';
|
||||||
import Slider from './Slider';
|
import Slider from './Slider';
|
||||||
|
import Announcement from './Announcement';
|
||||||
import { outfitURL } from '../utils/UrlGenerators';
|
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_MIN = 0.65;
|
||||||
const SIZE_RANGE = 0.55;
|
const SIZE_RANGE = 0.55;
|
||||||
@@ -53,7 +55,6 @@ function selectAll(e) {
|
|||||||
* Coriolis App Header section / menus
|
* Coriolis App Header section / menus
|
||||||
*/
|
*/
|
||||||
export default class Header extends TranslatedComponent {
|
export default class Header extends TranslatedComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -61,21 +62,14 @@ export default class Header extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
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._openShips = this._openMenu.bind(this, 's');
|
||||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||||
this._openComp = this._openMenu.bind(this, 'comp');
|
this._openComp = this._openMenu.bind(this, 'comp');
|
||||||
|
this._openAnnounce = this._openMenu.bind(this, 'announce');
|
||||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||||
this._showHelp = this._showHelp.bind(this);
|
|
||||||
this.languageOptions = [];
|
this.languageOptions = [];
|
||||||
this.insuranceOptions = [];
|
this.insuranceOptions = [];
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -205,13 +199,6 @@ export default class Header extends TranslatedComponent {
|
|||||||
Persist.showTooltips(!Persist.showTooltips());
|
Persist.showTooltips(!Persist.showTooltips());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle module resistances setting
|
|
||||||
*/
|
|
||||||
_toggleModuleResistances() {
|
|
||||||
Persist.showModuleResistances(!Persist.showModuleResistances());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show delete all modal
|
* Show delete all modal
|
||||||
* @param {SyntheticEvent} e Event
|
* @param {SyntheticEvent} e Event
|
||||||
@@ -221,20 +208,6 @@ export default class Header extends TranslatedComponent {
|
|||||||
this.context.showModal(<ModalDeleteAll />);
|
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
|
* Show export modal with detailed export
|
||||||
* @param {SyntheticEvent} e Event
|
* @param {SyntheticEvent} e Event
|
||||||
@@ -243,10 +216,22 @@ export default class Header extends TranslatedComponent {
|
|||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
const builds = chain(Persist.getBuilds())
|
||||||
|
.values()
|
||||||
|
.map((builds) => Object.values(builds))
|
||||||
|
.flatMap()
|
||||||
|
.map((code) => new Ship(code))
|
||||||
|
.value();
|
||||||
|
|
||||||
this.context.showModal(<ModalExport
|
this.context.showModal(<ModalExport
|
||||||
title={translate('detailed export')}
|
title={translate('detailed export')}
|
||||||
description={translate('PHRASE_EXPORT_DESC')}
|
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
|
* @return {React.Component} Menu
|
||||||
*/
|
*/
|
||||||
_getShipsMenu() {
|
_getShipsMenu() {
|
||||||
let shipList = [];
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
for (let s in Ships) {
|
|
||||||
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='menu-list dbl no-wrap' onClick={ (e) => e.stopPropagation() }>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -322,9 +302,10 @@ export default class Header extends TranslatedComponent {
|
|||||||
* @return {React.Component} Menu
|
* @return {React.Component} Menu
|
||||||
*/
|
*/
|
||||||
_getBuildsMenu() {
|
_getBuildsMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let builds = Persist.getBuilds();
|
let builds = Persist.getBuilds();
|
||||||
let buildList = [];
|
let buildList = [];
|
||||||
for (let shipId of this.shipOrder) {
|
for (let shipId of this.ships) {
|
||||||
if (builds[shipId]) {
|
if (builds[shipId]) {
|
||||||
let shipBuilds = [];
|
let shipBuilds = [];
|
||||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
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);
|
let href = outfitURL(shipId, builds[shipId][buildName], buildName);
|
||||||
shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className='block'>{buildName}</ActiveLink></li>);
|
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
|
* Generate the settings menu
|
||||||
* @return {React.Component} Menu
|
* @return {React.Component} Menu
|
||||||
@@ -379,7 +386,6 @@ export default class Header extends TranslatedComponent {
|
|||||||
_getSettingsMenu() {
|
_getSettingsMenu() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let tips = Persist.showTooltips();
|
let tips = Persist.showTooltips();
|
||||||
let moduleResistances = Persist.showModuleResistances();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
|
<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>{translate('tooltips')}</td>
|
||||||
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
|
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>{translate('insurance')}</td>
|
<td>{translate('insurance')}</td>
|
||||||
<td className='ri'>
|
<td className='ri'>
|
||||||
@@ -428,10 +430,9 @@ export default class Header extends TranslatedComponent {
|
|||||||
<hr />
|
<hr />
|
||||||
<ul style={{ width: '100%' }}>
|
<ul style={{ width: '100%' }}>
|
||||||
{translate('builds')} & {translate('comparisons')}
|
{translate('builds')} & {translate('comparisons')}
|
||||||
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showDetailedExport}>{translate('detailed export')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showImport}>{translate('import')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showDeleteAll}>{translate('delete all')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<hr />
|
<hr />
|
||||||
<table style={{ width: 300, backgroundColor: 'transparent' }}>
|
<table style={{ width: 300, backgroundColor: 'transparent' }}>
|
||||||
@@ -463,7 +464,6 @@ export default class Header extends TranslatedComponent {
|
|||||||
Persist.addListener('deletedAll', update);
|
Persist.addListener('deletedAll', update);
|
||||||
Persist.addListener('builds', update);
|
Persist.addListener('builds', update);
|
||||||
Persist.addListener('tooltips', 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
|
* Render the header
|
||||||
* @return {React.Component} Header
|
* @return {React.Component} Header
|
||||||
@@ -504,7 +513,10 @@ export default class Header extends TranslatedComponent {
|
|||||||
let hasBuilds = Persist.hasBuilds();
|
let hasBuilds = Persist.hasBuilds();
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
|
{this.props.appCacheUpdate && <div id="app-update" onClick={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>
|
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
|
||||||
|
|
||||||
<div className='l menu'>
|
<div className='l menu'>
|
||||||
@@ -521,12 +533,23 @@ export default class Header extends TranslatedComponent {
|
|||||||
{openedMenu == 'b' ? this._getBuildsMenu() : null}
|
{openedMenu == 'b' ? this._getBuildsMenu() : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 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 == 'announce' ? this._getAnnouncementsMenu() : null}
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
{window.location.origin.search('.edcd.io') >= 0 ?
|
||||||
<div className='l menu'>
|
<div className='l menu'>
|
||||||
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
|
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
|
||||||
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span>
|
<div className={cn('menu-header')}>
|
||||||
</div>
|
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
|
||||||
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
|
</div> : null
|
||||||
|
}
|
||||||
|
|
||||||
<div className='r menu'>
|
<div className='r menu'>
|
||||||
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
|
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
|
||||||
@@ -543,5 +566,4 @@ export default class Header extends TranslatedComponent {
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal Slot
|
|
||||||
*/
|
|
||||||
export default class InternalSlot extends Slot {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the slot contents
|
|
||||||
* @param {Object} m Mounted Module
|
|
||||||
* @param {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
|
||||||
if (m) {
|
|
||||||
let classRating = m.class + m.rating;
|
|
||||||
let { drag, drop, ship } = this.props;
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let validMods = (Modifications.modules[m.grp] ? Modifications.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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
|
|
||||||
<div className={'cb'}>
|
|
||||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
|
|
||||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
|
||||||
</div>
|
|
||||||
<div className={'cb'}>
|
|
||||||
{ m.getOptMass() ? <div className={'l'}>{translate('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 React from 'react';
|
||||||
import cn from 'classnames';
|
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import InternalSlot from './InternalSlot';
|
import Slot from './Slot';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
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
|
* Internal slot section
|
||||||
*/
|
*/
|
||||||
export default class InternalSlotSection extends SlotSection {
|
export default class InternalSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'internal', 'optional internal');
|
super(props, 'optional internal');
|
||||||
|
autoBind(this);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all slots
|
* Empty all slots
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.props.ship.emptyInternal();
|
this.props.ship.getModules(TYPES.ANY_INTERNAL).forEach((slot) => slot.reset());
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +49,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithCargo(event) {
|
_fillWithCargo(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'cargorack');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'cr')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,14 +59,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithFuelTanks(event) {
|
_fillWithFuelTanks(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'fueltank', '3');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'ft')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,14 +69,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithLuxuryCabins(event) {
|
_fillWithLuxuryCabins(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '4');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcq')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +79,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithFirstClassCabins(event) {
|
_fillWithFirstClassCabins(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '3');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcm')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +89,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithBusinessClassCabins(event) {
|
_fillWithBusinessClassCabins(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '2');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pci')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,14 +99,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithEconomyClassCabins(event) {
|
_fillWithEconomyClassCabins(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '1');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pce')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,17 +109,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithCells(event) {
|
_fillWithCells(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'scb', '5');
|
||||||
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();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,14 +119,8 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithArmor(event) {
|
_fillWithArmor(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'hrp', '2');
|
||||||
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();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,55 +129,31 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithModuleReinforcementPackages(event) {
|
_fillWithModuleReinforcementPackages(event) {
|
||||||
let clobber = event.getModifierState('Alt');
|
const fillAll = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
setAllEmpty(this.props.ship, fillAll, 'mrp', '2');
|
||||||
ship.internal.forEach((slot) => {
|
|
||||||
if ((clobber || !slot.m) && canMount(ship, slot, 'mrp')) {
|
|
||||||
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty all on section header right click
|
|
||||||
*/
|
|
||||||
_contextMenu() {
|
|
||||||
this._empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the slot React Components
|
* Generate the slot React Components
|
||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { currentMenu, ship } = this.props;
|
let { currentMenu, ship, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let { internal, fuelCapacity } = ship;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = internal.length; i < l; i++) {
|
for (const m of ship.getInternals(undefined, true)) {
|
||||||
let s = internal[i];
|
slots.push(<Slot
|
||||||
|
key={m.object.Slot}
|
||||||
slots.push(<InternalSlot
|
currentMenu={currentMenu}
|
||||||
key={i}
|
m={m}
|
||||||
maxClass={s.maxClass}
|
drag={this._drag.bind(this, m)}
|
||||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
dragOver={this._dragOverSlot.bind(this, m)}
|
||||||
onOpen={this._openMenu.bind(this,s)}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
onSelect={this._selectModule.bind(this, s)}
|
|
||||||
selected={currentMenu == s}
|
|
||||||
eligible={s.eligible}
|
|
||||||
m={s.m}
|
|
||||||
drag={this._drag.bind(this, s)}
|
|
||||||
dragOver={this._dragOverSlot.bind(this, s)}
|
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(s, originSlot, targetSlot)}
|
dropClass={this._dropClass(m, originSlot, targetSlot)}
|
||||||
fuel={fuelCapacity}
|
propsToShow={propsToShow}
|
||||||
ship={ship}
|
onPropToggle={onPropToggle}
|
||||||
enabled={s.enabled ? true : false}
|
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,22 +166,23 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {Function} ship The ship
|
* @param {Function} ship The ship
|
||||||
* @return {React.Component} Section menu
|
* @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}>
|
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
|
||||||
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
||||||
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithCells}>{translate('scb')}</li>
|
||||||
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
||||||
<li className='lc' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
||||||
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||||
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||||
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||||
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{translate('pcm')}</li>
|
||||||
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</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>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</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,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Measure from 'react-measure';
|
import ContainerDimensions from 'react-container-dimensions';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
|||||||
* Line Chart
|
* Line Chart
|
||||||
*/
|
*/
|
||||||
export default class LineChart extends TranslatedComponent {
|
export default class LineChart extends TranslatedComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
code: '',
|
code: '',
|
||||||
xMin: 0,
|
xMin: 0,
|
||||||
@@ -45,13 +45,7 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
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;
|
const series = props.series;
|
||||||
|
|
||||||
@@ -67,21 +61,17 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
xAxisScale,
|
xAxisScale,
|
||||||
yScale,
|
yScale,
|
||||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||||
dimensions: {
|
|
||||||
width: 100,
|
|
||||||
height: 100
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update tooltip content
|
* Update tooltip content
|
||||||
* @param {number} xPos x coordinate
|
* @param {number} xPos x coordinate
|
||||||
|
* @param {number} width current container width
|
||||||
*/
|
*/
|
||||||
_tooltip(xPos) {
|
_tooltip(xPos, width) {
|
||||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||||
let { xScale, yScale } = this.state;
|
let { xScale, yScale } = this.state;
|
||||||
let { width } = this.state.dimensions;
|
|
||||||
let { formats, translate } = this.context.language;
|
let { formats, translate } = this.context.language;
|
||||||
let x0 = xScale.invert(xPos),
|
let x0 = xScale.invert(xPos),
|
||||||
y0 = func(x0),
|
y0 = func(x0),
|
||||||
@@ -120,11 +110,11 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
* Update dimensions based on properties and scale
|
* Update dimensions based on properties and scale
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {number} scale size ratio / scale
|
* @param {number} scale size ratio / scale
|
||||||
|
* @param {number} width current width of the container
|
||||||
* @returns {Object} calculated dimensions
|
* @returns {Object} calculated dimensions
|
||||||
*/
|
*/
|
||||||
_updateDimensions(props, scale) {
|
_updateDimensions(props, scale, width) {
|
||||||
const { xMax, xMin, yMin, yMax } = props;
|
const { xMax, xMin, yMin, yMax } = props;
|
||||||
const { width, height } = this.state.dimensions;
|
|
||||||
const innerWidth = width - MARGIN.left - MARGIN.right;
|
const innerWidth = width - MARGIN.left - MARGIN.right;
|
||||||
const outerHeight = Math.round(width * props.aspect);
|
const outerHeight = Math.round(width * props.aspect);
|
||||||
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||||
@@ -149,10 +139,11 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
/**
|
/**
|
||||||
* Move and update tooltip
|
* Move and update tooltip
|
||||||
* @param {SyntheticEvent} e Event
|
* @param {SyntheticEvent} e Event
|
||||||
|
* @param {number} width current container width
|
||||||
*/
|
*/
|
||||||
_moveTip(e) {
|
_moveTip(e, width) {
|
||||||
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||||
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
|
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,18 +218,17 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
* @return {React.Component} Chart SVG
|
* @return {React.Component} Chart SVG
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
|
return (
|
||||||
const { width, height } = this.state.dimensions;
|
<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 { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
|
||||||
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||||
const 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 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 markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
|
||||||
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
|
||||||
<div width={width} height={height}>
|
<div width={width} height={height}>
|
||||||
<svg style={{ width: '100%', height: outerHeight }}>
|
<svg style={{ width: '100%', height: outerHeight }}>
|
||||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||||
@@ -271,13 +261,15 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
onTouchStart={this._showTip}
|
onTouchStart={this._showTip}
|
||||||
onMouseLeave={this._hideTip}
|
onMouseLeave={this._hideTip}
|
||||||
onTouchEnd={this._hideTip}
|
onTouchEnd={this._hideTip}
|
||||||
onMouseMove={this._moveTip}
|
onMouseMove={e => this._moveTip(e, width)}
|
||||||
onTouchMove={this._moveTip}
|
onTouchMove={e => this._moveTip(e, width)}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</Measure>
|
);
|
||||||
|
}}
|
||||||
|
</ContainerDimensions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Link wrapper component
|
* Link wrapper component
|
||||||
*/
|
*/
|
||||||
export default class Link extends React.Component {
|
export default class Link extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
@@ -56,5 +55,4 @@ export default class Link extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function buildComparator(a, b) {
|
|||||||
* Compare builds modal
|
* Compare builds modal
|
||||||
*/
|
*/
|
||||||
export default class ModalCompare extends TranslatedComponent {
|
export default class ModalCompare extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
builds: PropTypes.array
|
builds: PropTypes.array
|
||||||
@@ -105,8 +104,8 @@ export default class ModalCompare extends TranslatedComponent {
|
|||||||
|
|
||||||
let selectedBuilds = usedBuilds.map((build, i) =>
|
let selectedBuilds = usedBuilds.map((build, i) =>
|
||||||
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
||||||
<td className='tl'>{build.name}</td><
|
<td className='tl'>{build.name}</td>
|
||||||
td className='tl'>{build.buildName}</td>
|
<td className='tl'>{build.buildName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Delete All saved data modal
|
* Delete All saved data modal
|
||||||
*/
|
*/
|
||||||
export default class ModalDeleteAll extends TranslatedComponent {
|
export default class ModalDeleteAll extends TranslatedComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete everything and hide the modal
|
* Delete everything and hide the modal
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Export Modal
|
* Export Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalExport extends TranslatedComponent {
|
export default class ModalExport extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
generator: PropTypes.func,
|
generator: PropTypes.func,
|
||||||
|
|||||||
@@ -7,19 +7,10 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Help Modal
|
* Help Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalHelp extends TranslatedComponent {
|
export default class ModalHelp extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string
|
title: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modal
|
* Render the modal
|
||||||
* @return {React.Component} Modal Content
|
* @return {React.Component} Modal Content
|
||||||
|
|||||||
@@ -2,90 +2,23 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Router from '../Router';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
import autoBind from 'auto-bind';
|
||||||
import Ship from '../shipyard/Ship';
|
import { isArray } from 'lodash';
|
||||||
import { ModuleNameToGroup, Insurance } from '../shipyard/Constants';
|
import { Ship } from 'ed-forge';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
|
||||||
import { Download } from './SvgIcons';
|
|
||||||
import { outfitURL } from '../utils/UrlGenerators';
|
|
||||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
|
||||||
|
|
||||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
const STATE = {
|
||||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
READY: 0,
|
||||||
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
PARSED: 1,
|
||||||
const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 };
|
ERROR: 2,
|
||||||
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() };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import Modal
|
* Import Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalImport extends TranslatedComponent {
|
export default class ModalImport extends TranslatedComponent {
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
importString: PropTypes.string, // Optional: Default data for import modal
|
||||||
builds: PropTypes.object, // Optional: Import object
|
builds: PropTypes.object, // Optional: Import object
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,207 +28,36 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
builds: props.builds,
|
status: STATE.READY,
|
||||||
canEdit: !props.builds,
|
builds: props.builds || [],
|
||||||
comparisons: null,
|
|
||||||
shipDiscount: null,
|
|
||||||
moduleDiscount: null,
|
|
||||||
errorMsg: null,
|
|
||||||
importString: null,
|
|
||||||
importValid: false,
|
|
||||||
insurance: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
* Import SLEF formatted builds. Sets state to a map of the builds on success
|
||||||
* @param {Object} importData Backup Data
|
* and flags if there was only a single build.
|
||||||
* @throws {string} If import fails
|
*
|
||||||
*/
|
* @param {string} importData - Array of the list of builds.
|
||||||
_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
|
|
||||||
* @throws {string} If parse / import fails
|
* @throws {string} If parse / import fails
|
||||||
*/
|
*/
|
||||||
_importTextBuild(buildStr) {
|
_importSlefBuilds(importData) {
|
||||||
let buildName = textBuildRegex.exec(buildStr)[1].trim();
|
const builds = importData.reduce((memo, { data }) => {
|
||||||
let shipName = buildName.toLowerCase();
|
const shipModel = shipModelFromJson(data);
|
||||||
let shipId = null;
|
const ship = shipFromLoadoutJSON(data);
|
||||||
|
const shipTemplate = Ships[shipModel];
|
||||||
|
const shipName = data.ShipName || shipTemplate.properties.name;
|
||||||
|
|
||||||
for (let sId in Ships) {
|
const key = `Imported ${shipName}`;
|
||||||
if (Ships[sId].properties.name.toLowerCase() == shipName) {
|
memo[shipModel] = {};
|
||||||
shipId = sId;
|
memo[shipModel][key] = ship.toString();
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shipId) {
|
return memo;
|
||||||
throw 'No such ship found: "' + buildName + '"';
|
}, {});
|
||||||
}
|
|
||||||
|
|
||||||
let lines = buildStr.split('\n');
|
this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -303,158 +65,50 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
* @throws {string} If validation fails
|
* @throws {string} If validation fails
|
||||||
*/
|
*/
|
||||||
_validateImport(event) {
|
_parse(event) {
|
||||||
let importData = null;
|
const importString = event.target.value.trim();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
|
let data = JSON.parse(importString);
|
||||||
this._importTextBuild(importString);
|
if (!isArray(data)) {
|
||||||
} else { // JSON Build data
|
data = [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
|
const ships = data.map((item) => {
|
||||||
this._importCompanionApiBuild(importData); // Single sihp definition
|
try {
|
||||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
return new Ship(item.data ? item.data : item);
|
||||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
} catch (err) {
|
||||||
} else if (importData instanceof Array) { // Must be detailed export json
|
return err;
|
||||||
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({ ships, status: STATE.PARSED });
|
||||||
this.setState({ singleBuild: true });
|
} catch (err) {
|
||||||
} else { // Using Backup JSON
|
this.setState({ err, status: STATE.ERROR });
|
||||||
this._importBackup(importData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
// console.log(e.stack);
|
|
||||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ importValid: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process imported data
|
* Process imported data
|
||||||
*/
|
*/
|
||||||
_process() {
|
_process() {
|
||||||
let builds = null, comparisons = null;
|
for (const build of this.state.builds) {
|
||||||
|
if (!build instanceof Error) {
|
||||||
// If only importing a single build go straight to the outfitting page
|
Persist.saveBuild(build.Ship, build.CoriolisBuildName || build.ShipName, build.compress());
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.setState({ builds: [], status: STATE.READY });
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Capture build name changes
|
* Capture build name changes
|
||||||
* @param {Object} item Build/Comparison import object
|
* @param {Object} index Build/Comparison import object
|
||||||
* @param {SyntheticEvent} e Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_changeName(item, e) {
|
_changeName(index, event) {
|
||||||
item.useName = e.target.value;
|
const { builds } = this.state;
|
||||||
this.forceUpdate();
|
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
|
* If textarea is shown focus on mount
|
||||||
*/
|
*/
|
||||||
@@ -469,77 +123,34 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
* @return {React.Component} Modal contents
|
* @return {React.Component} Modal contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
const { translate } = this.context.language;
|
||||||
let state = this.state;
|
const { status, builds, err } = this.state;
|
||||||
let importStage;
|
|
||||||
|
|
||||||
if (!state.processed) {
|
const buildRows = builds.map((build, i) => {
|
||||||
importStage = (
|
if (build instanceof Error) {
|
||||||
|
return <tr key={i} className='cb'>
|
||||||
|
<td colSpan={3} className='warning'>Error: {build.name}</td>
|
||||||
|
</tr>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._parse} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||||
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
{status === STATE.ERROR && <div className='l warning' style={{ marginLeft:'3em' }}>{err.toString()}</div>}
|
||||||
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
{builds.length && <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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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%' }}>
|
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -552,17 +163,14 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
{buildRows}
|
{buildRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{comparisonTable}
|
</div>}
|
||||||
<button id='import' className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
|
<button id='proceed' className='l cap' onClick={this._process}
|
||||||
{edit}
|
disabled={status !== STATE.PARSED} >
|
||||||
</div>
|
{translate('proceed')}
|
||||||
);
|
</button>
|
||||||
}
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>
|
||||||
|
{translate('close')}
|
||||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
</button>
|
||||||
<h2 >{translate('import')}</h2>
|
|
||||||
{importStage}
|
|
||||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import ShortenUrl from '../utils/ShortenUrl';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalPermalink extends TranslatedComponent {
|
export default class ModalPermalink extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
url: PropTypes.string.isRequired
|
url: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
@@ -50,6 +49,7 @@ export default class ModalPermalink extends TranslatedComponent {
|
|||||||
<h3 >{translate('shortened')}</h3>
|
<h3 >{translate('shortened')}</h3>
|
||||||
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
|
||||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,63 +3,52 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import NumberEditor from 'react-number-editor';
|
import NumberEditor from 'react-number-editor';
|
||||||
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modification
|
* Modification
|
||||||
*/
|
*/
|
||||||
export default class Modification extends TranslatedComponent {
|
export default class Modification extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
highlight: PropTypes.bool,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.instanceOf(Module).isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
property: PropTypes.string.isRequired,
|
||||||
value: PropTypes.number.isRequired,
|
onSet: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
showProp: PropTypes.object,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
const { m, property, showProp } = props;
|
||||||
this.state.value = props.value;
|
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||||
|
this.state = { beneficial, unit, value, showProp };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update modification given a value.
|
* Notify listeners that a new value has been entered and commited.
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
_updateFinished() {
|
_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
|
* @return {React.Component} modification
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
const { formats } = this.context.language;
|
||||||
let { m, name } = this.props;
|
const { highlight, m, property } = this.props;
|
||||||
|
const { beneficial, unit, value, inputValue, showProp } = this.state;
|
||||||
|
|
||||||
if (name === 'damagedist') {
|
// Some features only apply to specific modules; these features will be
|
||||||
// We don't show damage distribution
|
// undefined on items that do not belong to the same class. Filter these
|
||||||
|
// features here
|
||||||
|
if (value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol;
|
const { value: modifierValue, unit: modifierUnit } = m.getModifierFormatted(property);
|
||||||
if (name === 'jitter') {
|
|
||||||
symbol = '°';
|
|
||||||
} else if (name !== 'burst' && name != 'burstrof') {
|
|
||||||
symbol = '%';
|
|
||||||
}
|
|
||||||
if (symbol) {
|
|
||||||
symbol = ' (' + symbol + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
|
<tr>
|
||||||
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
|
<td>
|
||||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
<span>
|
||||||
</div>
|
<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 React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as _ from 'lodash';
|
import { chain, flatMap, keys } from 'lodash';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import Modification from './Modification';
|
import Modification from './Modification';
|
||||||
import { getBlueprint, blueprintTooltip, setPercent, 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
|
* Modifications menu
|
||||||
*/
|
*/
|
||||||
export default class ModificationsMenu extends TranslatedComponent {
|
export default class ModificationsMenu extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
className: PropTypes.string,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.object.isRequired,
|
||||||
marker: PropTypes.string.isRequired,
|
propsToShow: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,50 +34,57 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
|
|
||||||
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
||||||
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
||||||
this._rollFifty = this._rollFifty.bind(this);
|
this.selectedModRef = null;
|
||||||
this._rollRandom = this._rollRandom.bind(this);
|
this.selectedSpecialRef = null;
|
||||||
this._rollBest = this._rollBest.bind(this);
|
|
||||||
this._rollWorst = this._rollWorst.bind(this);
|
|
||||||
this._reset = this._reset.bind(this);
|
|
||||||
|
|
||||||
|
const { m } = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
|
blueprintProgress: m.getBlueprintProgress(),
|
||||||
|
blueprintMenuOpened: !m.getBlueprint(),
|
||||||
specialMenuOpened: false
|
specialMenuOpened: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the blueprints
|
* Render the blueprints
|
||||||
* @param {Object} props React component properties
|
|
||||||
* @param {Object} context React component context
|
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderBlueprints(props, context) {
|
_renderBlueprints() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const { translate } = language;
|
||||||
|
|
||||||
const blueprints = [];
|
const blueprints = m.getApplicableBlueprints().map(blueprint => {
|
||||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
const info = getBlueprintInfo(blueprint);
|
||||||
const blueprint = getBlueprint(blueprintName, m);
|
let blueprintGrades = keys(info.features).map(grade => {
|
||||||
let blueprintGrades = [];
|
|
||||||
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
|
|
||||||
// Grade is a string in the JSON so make it a number
|
// Grade is a string in the JSON so make it a number
|
||||||
grade = Number(grade);
|
grade = Number(grade);
|
||||||
const classes = cn('c', {
|
const active = m.getBlueprint() === blueprint && m.getBlueprintGrade() === grade;
|
||||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.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,
|
||||||
});
|
});
|
||||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
}}
|
||||||
const key = blueprintName + ':' + grade;
|
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
|
||||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
>{grade}</li>;
|
||||||
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) {
|
return [
|
||||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
<div key={'div' + blueprint} className={'select-group cap'}>
|
||||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
{translate(blueprint)}
|
||||||
}
|
</div>,
|
||||||
}
|
<ul key={'ul' + blueprint}>{blueprintGrades}</ul>
|
||||||
return blueprints;
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return flatMap(blueprints);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,140 +93,133 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
* @param {Object} context React component context
|
* @param {Object} context React component context
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderSpecials(props, context) {
|
_renderSpecials() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
|
|
||||||
const specials = [];
|
const applied = m.getExperimental();
|
||||||
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
|
const experimentals = [];
|
||||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
for (const experimental of m.getApplicableExperimentals()) {
|
||||||
const close = this._specialSelected.bind(this, null);
|
const active = experimental === applied;
|
||||||
specials.push(<div style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
|
let specialTt = specialToolTip(language, m, experimental);
|
||||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
experimentals.push(
|
||||||
const classes = cn('button-inline-menu', {
|
<div key={experimental} data-id={experimental}
|
||||||
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
|
style={{ cursor: 'pointer' }}
|
||||||
});
|
className={cn('button-inline-menu', { active })}
|
||||||
const close = this._specialSelected.bind(this, specialName);
|
onClick={this._specialSelected(experimental)}
|
||||||
specials.push(<div style={{ cursor: 'pointer' }} className={classes} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
|
ref={active ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||||
|
onMouseOver={termtip.bind(null, specialTt)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate(experimental)}</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 specials;
|
|
||||||
|
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
|
* Render the modifications
|
||||||
* @param {Object} props React Component properties
|
* @return {Array} Array of React Components
|
||||||
* @return {Object} list: Array of React Components
|
|
||||||
*/
|
*/
|
||||||
_renderModifications(props) {
|
_renderModifications() {
|
||||||
const { m, onChange, ship } = props;
|
const { m } = this.props;
|
||||||
const modifications = [];
|
|
||||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
const blueprintFeatures = getBlueprintInfo(m.getBlueprint()).features[
|
||||||
if (!Modifications.modifications[modName].hidden) {
|
m.getBlueprintGrade()
|
||||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
];
|
||||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
|
const blueprintModifications = chain(keys(blueprintFeatures))
|
||||||
}
|
.map((feature) => this._mkModification(feature, true))
|
||||||
}
|
.filter(([_, mod]) => Boolean(mod))
|
||||||
return modifications;
|
.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
|
* Toggle the blueprints menu
|
||||||
*/
|
*/
|
||||||
_toggleBlueprintsMenu() {
|
_toggleBlueprintsMenu() {
|
||||||
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
|
this.setState({ 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);
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
|
|
||||||
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the specials menu
|
* Toggle the specials menu
|
||||||
*/
|
*/
|
||||||
_toggleSpecialsMenu() {
|
_toggleSpecialsMenu() {
|
||||||
const specialMenuOpened = !this.state.specialMenuOpened;
|
this.setState({ specialMenuOpened: !this.state.specialMenuOpened });
|
||||||
this.setState({ specialMenuOpened });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activated when a special is selected
|
* Creates a callback for when a special effect is being selected
|
||||||
* @param {int} special The name of the selected special
|
* @param {string} special The name of the selected special
|
||||||
|
* @returns {function} Callback
|
||||||
*/
|
*/
|
||||||
_specialSelected(special) {
|
_specialSelected(special) {
|
||||||
this.context.tooltip(null);
|
return () => {
|
||||||
const { m, ship } = this.props;
|
const { m } = this.props;
|
||||||
|
m.setExperimental(special);
|
||||||
if (special === null) {
|
|
||||||
ship.clearModuleSpecial(m);
|
|
||||||
} else {
|
|
||||||
ship.setModuleSpecial(m, Modifications.specials[special]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ specialMenuOpened: false });
|
this.setState({ specialMenuOpened: false });
|
||||||
this.props.onChange();
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a '50%' roll within the information we have
|
* Set focus on first element in modifications menu
|
||||||
|
* if component updates, unless update is due to value change
|
||||||
|
* in a modification
|
||||||
*/
|
*/
|
||||||
_rollFifty() {
|
componentDidUpdate() {
|
||||||
const { m, ship } = this.props;
|
if (this.selectedModRef) {
|
||||||
setPercent(ship, m, 50);
|
this.selectedModRef.focus();
|
||||||
this.props.onChange();
|
return;
|
||||||
|
} else if (this.selectedSpecialRef) {
|
||||||
|
this.selectedSpecialRef.focus();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a 'worst' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollWorst() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 0);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset modification information
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
ship.clearModifications(m);
|
|
||||||
ship.clearModuleBlueprint(m);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,74 +230,155 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
const { m } = this.props;
|
const { m } = this.props;
|
||||||
const { blueprintMenuOpened, specialMenuOpened } = this.state;
|
const {
|
||||||
|
blueprintProgress, blueprintMenuOpened, specialMenuOpened,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
const appliedBlueprint = m.getBlueprint();
|
||||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
const appliedGrade = m.getBlueprintGrade();
|
||||||
const _rollFull = this._rollBest;
|
const appliedExperimental = m.getExperimental();
|
||||||
const _rollWorst = this._rollWorst;
|
|
||||||
const _rollFifty = this._rollFifty;
|
|
||||||
const _rollRandom = this._rollRandom;
|
|
||||||
const _reset = this._reset;
|
|
||||||
|
|
||||||
let blueprintLabel;
|
let renderComponents = [];
|
||||||
let haveBlueprint = false;
|
switch (true) {
|
||||||
let blueprintTt;
|
case !appliedBlueprint || blueprintMenuOpened:
|
||||||
if (m.blueprint && m.blueprint.name) {
|
renderComponents = this._renderBlueprints();
|
||||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
break;
|
||||||
haveBlueprint = true;
|
case specialMenuOpened:
|
||||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
|
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);
|
||||||
|
|
||||||
let specialLabel;
|
renderComponents.push(
|
||||||
if (m.blueprint && m.blueprint.special) {
|
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
|
||||||
specialLabel = m.blueprint.special.name;
|
className="section-menu button-inline-menu"
|
||||||
} else {
|
onMouseOver={termtip.bind(null, blueprintTt)}
|
||||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
}
|
onClick={this._toggleBlueprintsMenu}
|
||||||
|
|
||||||
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}
|
|
||||||
>
|
>
|
||||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
{translate(appliedBlueprint)} {translate('grade')} {appliedGrade}
|
||||||
<div className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
|
</div>
|
||||||
<div className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
);
|
||||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
|
||||||
{ showSpecial & !showSpecialsMenu ? <div className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
|
if (m.getApplicableExperimentals().length) {
|
||||||
{ showSpecialsMenu ? specials : null }
|
let specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||||
{ showRolls || showReset ?
|
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' }}>
|
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ showRolls ?
|
|
||||||
<tr>
|
<tr>
|
||||||
<td> { translate('roll') }: </td>
|
<td
|
||||||
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
className={cn(
|
||||||
<td style={{ cursor: 'pointer' }} onClick={_rollFifty} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
'section-menu button-inline-menu',
|
||||||
<td style={{ cursor: 'pointer' }} onClick={_rollFull} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
{ active: false },
|
||||||
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
)}
|
||||||
</tr> : null }
|
>{translate('mroll')}:</td>
|
||||||
{ showReset ?
|
<td
|
||||||
<tr>
|
className={cn(
|
||||||
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
|
'section-menu button-inline-menu',
|
||||||
</tr> : null }
|
{ 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>
|
</tbody>
|
||||||
</table> : null }
|
</table>,
|
||||||
{ showMods ?
|
<hr />,
|
||||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
<span
|
||||||
{ this._renderModifications(this.props) }
|
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')}
|
||||||
</span> : null }
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<table style={{ width: '100%' }}>
|
||||||
|
<tbody>
|
||||||
|
{this._renderModifications()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('select', this.props.className)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onContextMenu={stopCtxPropagation}
|
||||||
|
>
|
||||||
|
{renderComponents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,39 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { SPEED, BOOST_SPEED, ROLL, BOOST_ROLL, YAW, BOOST_YAW, PITCH, BOOST_PITCH } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement
|
* Movement
|
||||||
*/
|
*/
|
||||||
export default class Movement extends TranslatedComponent {
|
export default class Movement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
cargo: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render movement
|
* Render movement
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, boost, eng, cargo, fuel } = this.props;
|
const { ship, boost } = this.props;
|
||||||
const { language } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats } = language;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='movement'>
|
<span id='movement'>
|
||||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||||
// Axes
|
{/* Axes */}
|
||||||
<path d="M150 250v300" strokeWidth='1'/>
|
<path d="M150 250v300" strokeWidth='1'/>
|
||||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||||
<path d="M150 250l350 -200" 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"/>
|
<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="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="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
||||||
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
<path d="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="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"/>
|
<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.get(boost ? BOOST_SPEED : SPEED)) + 'm/s'}</text>
|
||||||
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
|
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_PITCH : PITCH)) + '°/s'}</text>
|
||||||
// Pitch
|
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_ROLL : ROLL)) + '°/s'}</text>
|
||||||
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_YAW : YAW)) + '°/s'}</text>
|
||||||
// Roll
|
|
||||||
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
// Yaw
|
|
||||||
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
</span>);
|
</span>);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,35 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
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
|
* Turns an object into a tooltip.
|
||||||
* sort by specified property (if provided) then by name/group, class, rating
|
* @param {function} translate Translate function
|
||||||
* @param {function} translate Translation function
|
* @param {object} o Map to make the tooltip from
|
||||||
* @param {function} propComparator Optional property comparator
|
* @returns {React.Component} Tooltip
|
||||||
* @param {boolean} desc Use descending order
|
|
||||||
* @return {function} Comparator function for names
|
|
||||||
*/
|
*/
|
||||||
export function weaponComparator(translate, propComparator, desc) {
|
function objToTooltip(translate, o) {
|
||||||
return (a, b) => {
|
return toPairs(o)
|
||||||
if (!desc) { // Flip A and B if ascending order
|
.filter(([k, v]) => Boolean(v))
|
||||||
let t = a;
|
.map(([k, v]) => <div key={k}>{`${translate(k)}: ${v}`}</div>);
|
||||||
a = b;
|
}
|
||||||
b = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a property comparator is provided use it first
|
/**
|
||||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||||
|
* @param {function} translate Translation function
|
||||||
if (diff) {
|
* @param {Calc.SDps} o Object that holds sdps split up by type
|
||||||
return diff;
|
* @returns {Object} Data object
|
||||||
}
|
*/
|
||||||
|
function objToPie(translate, o) {
|
||||||
// Property matches so sort by name / group, then class, rating
|
return toPairs(o).map(([k, value]) => {
|
||||||
if (a.name === b.name && a.grp === b.grp) {
|
return { label: translate(k), value };
|
||||||
if(a.class == b.class) {
|
});
|
||||||
return a.rating > b.rating ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.class - b.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nameComparator(translate, a, b);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,12 +42,10 @@ export function weaponComparator(translate, propComparator, desc) {
|
|||||||
*/
|
*/
|
||||||
export default class Offence extends TranslatedComponent {
|
export default class Offence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.instanceOf(Ship).isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
|
||||||
opponentSys: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,163 +54,234 @@ export default class Offence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(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 = {
|
this.state = {
|
||||||
predicate: 'n',
|
predicate: 'classRating',
|
||||||
desc: true,
|
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
|
* Set the sort order and sort
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
let desc = this.state.desc;
|
let desc = predicate == this.state.predicate ? !this.state.desc : true;
|
||||||
|
|
||||||
if (predicate == this.state.predicate) {
|
|
||||||
desc = !desc;
|
|
||||||
} else {
|
|
||||||
desc = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sort(predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the weapon list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
|
||||||
_sort(predicate, desc) {
|
|
||||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
|
||||||
|
|
||||||
switch (predicate) {
|
|
||||||
case 'n': comp = comp(null, desc); break;
|
|
||||||
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
|
|
||||||
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
|
|
||||||
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
|
|
||||||
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.damage.sort(comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render offence
|
* Render offence
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, opponent, wep, engagementrange } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { damage } = this.state;
|
|
||||||
const sortOrder = this._sortOrder;
|
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 oppShield = ship.getOpponent().getShield();
|
||||||
const opponentArmour = Calc.armourMetrics(opponent);
|
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;
|
const weapons = sortBy(ship.getHardpoints(), (m) => m.get('distributordraw'));
|
||||||
let explosiveShieldsSDps = 0;
|
let rows = weapons.map((weapon) => {
|
||||||
let kineticShieldsSDps = 0;
|
const sdps = weapon.get('sustaineddamagepersecond');
|
||||||
let thermalShieldsSDps = 0;
|
const byRange = weapon.getRangeEffectiveness();
|
||||||
let absoluteArmourSDps = 0;
|
const weaponPortions = {
|
||||||
let explosiveArmourSDps = 0;
|
Absolute: weapon.get('absolutedamageportion'),
|
||||||
let kineticArmourSDps = 0;
|
Explosive: weapon.get('explosivedamageportion'),
|
||||||
let thermalArmourSDps = 0;
|
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 = [];
|
const byHardness = weapon.getArmourEffectiveness();
|
||||||
for (let i = 0; i < damage.length; i++) {
|
const armourResEfts = mergeWith(
|
||||||
const weapon = damage[i];
|
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;
|
const bp = weapon.getBlueprint();
|
||||||
absoluteShieldsSDps += weapon.sdps.shields.absolute;
|
const grade = weapon.getBlueprintGrade();
|
||||||
explosiveShieldsSDps += weapon.sdps.shields.explosive;
|
const exp = weapon.getExperimental();
|
||||||
kineticShieldsSDps += weapon.sdps.shields.kinetic;
|
let bpTitle = `${translate(bp)} ${translate('grade')} ${grade}`;
|
||||||
thermalShieldsSDps += weapon.sdps.shields.thermal;
|
if (exp) {
|
||||||
absoluteArmourSDps += weapon.sdps.armour.absolute;
|
bpTitle += `, ${translate(exp)}`;
|
||||||
explosiveArmourSDps += weapon.sdps.armour.explosive;
|
}
|
||||||
kineticArmourSDps += weapon.sdps.armour.kinetic;
|
return {
|
||||||
thermalArmourSDps += weapon.sdps.armour.thermal;
|
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 = [];
|
const { predicate, desc } = this.state;
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
rows = sortBy(rows, (row) => row[predicate]);
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
|
if (desc) {
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
|
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 effectivenessArmourTooltipDetails = [];
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
|
||||||
const effectiveArmourSDpsTooltipDetails = [];
|
|
||||||
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
|
|
||||||
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
|
|
||||||
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
|
|
||||||
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
|
|
||||||
|
|
||||||
rows.push(
|
|
||||||
<tr key={weapon.id}>
|
|
||||||
<td className='ri'>
|
|
||||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
|
||||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
|
||||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
|
||||||
{weapon.classRating} {translate(weapon.name)}
|
|
||||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
|
||||||
</td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
|
||||||
</tr>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
|
const sdpsTooltip = objToTooltip(
|
||||||
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
|
translate,
|
||||||
|
mapValues(portions, (p) => formats.f1(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
const sdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(portions, (p) => Math.round(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
|
||||||
const shieldsSDpsData = [];
|
const shieldSdpsSrcs = mergeWith(
|
||||||
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
|
clone(portions),
|
||||||
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
|
shieldMults,
|
||||||
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
|
(objV, srcV) => sustained.dps * oppShield.absolute.bySys *
|
||||||
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
|
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)),
|
||||||
|
);
|
||||||
|
|
||||||
const armourSDpsData = [];
|
const armourSdpsSrcs = mergeWith(
|
||||||
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
|
clone(portions),
|
||||||
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
|
armourMults,
|
||||||
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
|
(objV, srcV) => sustained.dps * hardnessMultiplier * rangeMultiplier *
|
||||||
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
|
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 timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
const drainedPortions = {
|
||||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
Absolute: drained.types.abs,
|
||||||
|
Explosive: drained.types.expl,
|
||||||
|
Kinetic: drained.types.kin,
|
||||||
|
Thermic: drained.types.therm,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<span id='offence'>
|
<span id='offence'>
|
||||||
@@ -230,36 +289,129 @@ export default class Offence extends TranslatedComponent {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'classRating')}>{translate('weapon')}</th>
|
||||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
<th colSpan='1'>{translate('overall')}</th>
|
||||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
<th colSpan='3'>{translate('opponent\'s shields')}</th>
|
||||||
|
<th colSpan='3'>{translate('opponent\'s armour')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'sdps')}>sdps</th>
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('offence metrics')}</h2>
|
<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_TIME_TO_DRAIN_WEP'))}
|
||||||
<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>
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
<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>
|
{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>
|
||||||
<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>
|
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
|
||||||
<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>
|
||||||
|
<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>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))}
|
||||||
<PieChart data={shieldsSDpsData} />
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('overall damage')}
|
||||||
|
</h2>
|
||||||
|
<PieChart data={sdpsPie} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<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} />
|
<PieChart data={armourSDpsData} />
|
||||||
</div>
|
</div>
|
||||||
</span>);
|
</span>);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import PowerManagement from './PowerManagement';
|
import PowerManagement from './PowerManagement';
|
||||||
@@ -13,29 +11,24 @@ import Movement from './Movement';
|
|||||||
import Offence from './Offence';
|
import Offence from './Offence';
|
||||||
import Defence from './Defence';
|
import Defence from './Defence';
|
||||||
import WeaponDamageChart from './WeaponDamageChart';
|
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
|
* Outfitting subpages
|
||||||
*/
|
*/
|
||||||
export default class OutfittingSubpages extends TranslatedComponent {
|
export default class OutfittingSubpages extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
buildName: PropTypes.string,
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._powerTab = this._powerTab.bind(this);
|
autoBind(this);
|
||||||
this._profilesTab = this._profilesTab.bind(this);
|
|
||||||
this._offenceTab = this._offenceTab.bind(this);
|
|
||||||
this._defenceTab = this._defenceTab.bind(this);
|
|
||||||
|
|
||||||
|
this.props.ship.setOpponent(this.props.ship);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
boost: false,
|
||||||
|
cargo: props.ship.get(CARGO_CAPACITY),
|
||||||
|
fuel: props.ship.get(FUEL_CAPACITY),
|
||||||
|
pips: props.ship.getDistributorSettingsObject(),
|
||||||
tab: Persist.getOutfittingTab() || 'power',
|
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
|
* @param {string} tab Tab name
|
||||||
*/
|
*/
|
||||||
_showTab(tab) {
|
_showTab(tab) {
|
||||||
|
Persist.setOutfittingTab(tab);
|
||||||
this.setState({ 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
|
* Render the section
|
||||||
* @return {React.Component} Contents
|
* @return {React.Component} Contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const tab = this.state.tab;
|
const { buildName, code, ship } = this.props;
|
||||||
const translate = this.context.language.translate;
|
const { boost, cargo, fuel, pips, tab, engagementRange, opponent } = this.state;
|
||||||
let tabSection;
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
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 cargoCapacity = ship.get(CARGO_CAPACITY);
|
||||||
|
const showCargoSlider = cargoCapacity > 0;
|
||||||
return (
|
return (
|
||||||
|
<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' }}>
|
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||||
<table className='tabs'>
|
<table className='tabs'>
|
||||||
|
{/* Select tab section */}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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 == 'power' })}
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
onClick={this._showTab.bind(this, 'power')}>
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
{translate('power and costs')}
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
{tabSection}
|
{/* 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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Measure from 'react-measure';
|
import ContainerDimensions from 'react-container-dimensions';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||||
@@ -10,7 +10,6 @@ const LABEL_COLOUR = '#000000';
|
|||||||
* A pie chart
|
* A pie chart
|
||||||
*/
|
*/
|
||||||
export default class PieChart extends Component {
|
export default class PieChart extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
data : PropTypes.array.isRequired
|
data : PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
@@ -27,13 +26,6 @@ export default class PieChart extends Component {
|
|||||||
this.colors = CORIOLIS_COLOURS;
|
this.colors = CORIOLIS_COLOURS;
|
||||||
this.arc = d3.arc();
|
this.arc = d3.arc();
|
||||||
this.arc.innerRadius(0);
|
this.arc.innerRadius(0);
|
||||||
|
|
||||||
this.state = {
|
|
||||||
dimensions: {
|
|
||||||
width: 100,
|
|
||||||
height: 100
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -41,15 +33,15 @@ export default class PieChart extends Component {
|
|||||||
* Generate a slice of the pie chart
|
* Generate a slice of the pie chart
|
||||||
* @param {Object} d the data for this slice
|
* @param {Object} d the data for this slice
|
||||||
* @param {number} i the index of 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
|
* @returns {Object} the SVG for the slice
|
||||||
*/
|
*/
|
||||||
sliceGenerator(d, i) {
|
sliceGenerator(d, i, width) {
|
||||||
if (!d || d.value == 0) {
|
if (!d || d.value == 0) {
|
||||||
// Ignore 0 values
|
// Ignore 0 values
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { width, height } = this.state.dimensions;
|
|
||||||
const { data } = this.props;
|
const { data } = this.props;
|
||||||
|
|
||||||
// Push the labels further out from the centre of the slice
|
// Push the labels further out from the centre of the slice
|
||||||
@@ -76,22 +68,24 @@ export default class PieChart extends Component {
|
|||||||
* @returns {object} Markup
|
* @returns {object} Markup
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { width, height } = this.state.dimensions;
|
return (
|
||||||
|
<ContainerDimensions>
|
||||||
|
{ ({ width }) => {
|
||||||
const pie = this.pie(this.props.data),
|
const pie = this.pie(this.props.data),
|
||||||
translate = `translate(${width / 2}, ${width * 0.4})`;
|
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||||
|
|
||||||
this.arc.outerRadius(width * 0.4);
|
this.arc.outerRadius(width * 0.4);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
|
||||||
<div width={width} height={width}>
|
<div width={width} height={width}>
|
||||||
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
||||||
<g transform={translate}>
|
<g transform={translate}>
|
||||||
{pie.map((d, i) => this.sliceGenerator(d, i))}
|
{pie.map((d, i) => this.sliceGenerator(d, i, width))}
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</Measure>
|
);
|
||||||
|
}}
|
||||||
|
</ContainerDimensions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Pip } from './SvgIcons';
|
import { Pip } from './SvgIcons';
|
||||||
import LineChart from '../components/LineChart';
|
import { autoBind } from 'react-extras';
|
||||||
import Slider from '../components/Slider';
|
import { Ship } from 'ed-forge';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
* 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 {
|
export default class Pips extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
sys: PropTypes.number.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,9 +23,13 @@ export default class Pips extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
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
|
* Creates a function that handles pip assignment and call `onChance`.
|
||||||
* @param {string} which Which item was clicked
|
* @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) {
|
_change(cb, isMc) {
|
||||||
if (which == 'SYS') {
|
return () => {
|
||||||
this._incSys();
|
cb(isMc);
|
||||||
} else if (which == 'ENG') {
|
this.props.onChange(this.props.ship.getDistributorSettingsObject());
|
||||||
this._incEng();
|
};
|
||||||
} else if (which == 'WEP') {
|
|
||||||
this._incWep();
|
|
||||||
} else if (which == 'RST') {
|
|
||||||
this._reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the capacitor
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
if (sys != 2 || eng != 2 || wep != 2) {
|
|
||||||
sys = eng = wep = 2;
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the SYS capacitor
|
|
||||||
*/
|
|
||||||
_incSys() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - sys);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (eng > wep) {
|
|
||||||
eng -= 0.5;
|
|
||||||
sys += 0.5;
|
|
||||||
} else {
|
|
||||||
wep -= 0.5;
|
|
||||||
sys += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (eng == 0) {
|
|
||||||
wep -= 1;
|
|
||||||
sys += 1;
|
|
||||||
} else if (wep == 0) {
|
|
||||||
eng -= 1;
|
|
||||||
sys += 1;
|
|
||||||
} else {
|
|
||||||
eng -= 0.5;
|
|
||||||
wep -= 0.5;
|
|
||||||
sys += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the ENG capacitor
|
|
||||||
*/
|
|
||||||
_incEng() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - eng);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (sys > wep) {
|
|
||||||
sys -= 0.5;
|
|
||||||
eng += 0.5;
|
|
||||||
} else {
|
|
||||||
wep -= 0.5;
|
|
||||||
eng += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (sys == 0) {
|
|
||||||
wep -= 1;
|
|
||||||
eng += 1;
|
|
||||||
} else if (wep == 0) {
|
|
||||||
sys -= 1;
|
|
||||||
eng += 1;
|
|
||||||
} else {
|
|
||||||
sys -= 0.5;
|
|
||||||
wep -= 0.5;
|
|
||||||
eng += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the WEP capacitor
|
|
||||||
*/
|
|
||||||
_incWep() {
|
|
||||||
let { sys, eng, wep } = this.props;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - wep);
|
|
||||||
if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (sys > eng) {
|
|
||||||
sys -= 0.5;
|
|
||||||
wep += 0.5;
|
|
||||||
} else {
|
|
||||||
eng -= 0.5;
|
|
||||||
wep += 0.5;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (sys == 0) {
|
|
||||||
eng -= 1;
|
|
||||||
wep += 1;
|
|
||||||
} else if (eng == 0) {
|
|
||||||
sys -= 1;
|
|
||||||
wep += 1;
|
|
||||||
} else {
|
|
||||||
sys -= 0.5;
|
|
||||||
eng -= 0.5;
|
|
||||||
wep += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.props.onChange(sys, eng, wep);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the rendering for pips
|
* 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
|
* @returns {Object} Object containing the rendering for the pips
|
||||||
*/
|
*/
|
||||||
_renderPips(sys, eng, wep) {
|
_renderPips() {
|
||||||
const pipsSvg = {};
|
const pipsSvg = {
|
||||||
|
Sys: [],
|
||||||
|
Eng: [],
|
||||||
|
Wep: [],
|
||||||
|
};
|
||||||
|
|
||||||
// SYS
|
for (let k in this.props.pips) {
|
||||||
pipsSvg['SYS'] = [];
|
let { base, mc } = this.props.pips[k];
|
||||||
for (let i = 0; i < Math.floor(sys); i++) {
|
for (let i = 0; i < Math.floor(base); i++) {
|
||||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
pipsSvg[k].push(<Pip key={i} className='full' />);
|
||||||
}
|
}
|
||||||
if (sys > Math.floor(sys)) {
|
if (base > Math.floor(base)) {
|
||||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
pipsSvg[k].push(<Pip className='half' key={'half'} />);
|
||||||
}
|
}
|
||||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
for (let i = 0; i < mc; i++) {
|
||||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
pipsSvg[k].push(<Pip key={base + i} className='mc' />);
|
||||||
}
|
}
|
||||||
|
for (let i = Math.ceil(base + mc); i < 4; i++) {
|
||||||
// ENG
|
pipsSvg[k].push(<Pip className='empty' key={i} />);
|
||||||
pipsSvg['ENG'] = [];
|
|
||||||
for (let i = 0; i < Math.floor(eng); i++) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
|
||||||
}
|
}
|
||||||
if (eng > Math.floor(eng)) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
|
||||||
}
|
|
||||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
|
||||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WEP
|
|
||||||
pipsSvg['WEP'] = [];
|
|
||||||
for (let i = 0; i < Math.floor(wep); i++) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
|
||||||
}
|
|
||||||
if (wep > Math.floor(wep)) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
|
||||||
}
|
|
||||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
|
||||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pipsSvg;
|
return pipsSvg;
|
||||||
@@ -260,15 +121,10 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { ship } = this.props;
|
||||||
const { sys, eng, wep } = this.props;
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
const onSysClicked = this.onClick.bind(this, 'SYS');
|
const pipsSvg = this._renderPips();
|
||||||
const onEngClicked = this.onClick.bind(this, 'ENG');
|
|
||||||
const onWepClicked = this.onClick.bind(this, 'WEP');
|
|
||||||
const onRstClicked = this.onClick.bind(this, 'RST');
|
|
||||||
|
|
||||||
const pipsSvg = this._renderPips(sys, eng, wep);
|
|
||||||
return (
|
return (
|
||||||
<span id='pips'>
|
<span id='pips'>
|
||||||
<table>
|
<table>
|
||||||
@@ -276,20 +132,40 @@ export default class Pips extends TranslatedComponent {
|
|||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
|
<td className='clickable' onClick={this._incEng}>{pipsSvg.Eng}</td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
|
<td className='clickable' onClick={this._incSys}>{pipsSvg.Sys}</td>
|
||||||
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
|
<td className='clickable' onClick={this._incEng}>
|
||||||
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
|
{translate('ENG')}
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._incWep}>{pipsSvg.Wep}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
|
<td className='clickable' onClick={this._incSys}>
|
||||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
{translate('SYS')}
|
||||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
</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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -4,27 +4,25 @@ import * as d3 from 'd3';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
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 {Boolean} selected Band selected
|
||||||
* @param {number} sum Band power sum
|
* @param {Number} relDraw Relative amount of power drawn by this band and
|
||||||
* @param {number} avail Total available power
|
* all prior
|
||||||
* @return {string} CSS Class name
|
* @return {string} CSS Class name
|
||||||
*/
|
*/
|
||||||
function getClass(selected, sum, avail) {
|
function getClass(selected, relDraw) {
|
||||||
return selected ? 'secondary' : ((Math.round(sum * 100) / 100) >= avail) ? 'warning' : 'primary';
|
if (selected) {
|
||||||
}
|
return 'secondary';
|
||||||
|
} else if (relDraw >= 1) {
|
||||||
/**
|
return 'warning';
|
||||||
* Get the # label for a Priority band
|
} else {
|
||||||
* @param {number} val Priority Band Watt value
|
return 'primary';
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,12 +30,10 @@ function bandText(val, index, wattScale) {
|
|||||||
* Renders the SVG to simulate in-game power bands
|
* Renders the SVG to simulate in-game power bands
|
||||||
*/
|
*/
|
||||||
export default class PowerBands extends TranslatedComponent {
|
export default class PowerBands extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
bands: PropTypes.array.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
available: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
width: PropTypes.number.isRequired,
|
width: PropTypes.number.isRequired,
|
||||||
code: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,20 +43,16 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.wattScale = d3.scaleLinear();
|
this.wattScale = d3.scaleLinear();
|
||||||
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
||||||
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
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.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();
|
this._hidetip = () => this.context.tooltip();
|
||||||
|
|
||||||
let maxBand = props.bands[props.bands.length - 1];
|
this.profile = props.ship.getMetrics(POWER_METRICS);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
|
||||||
ret: {},
|
ret: {},
|
||||||
dep: {}
|
dep: {}
|
||||||
};
|
};
|
||||||
@@ -84,8 +76,6 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
let mRight = Math.round(140 * scale);
|
let mRight = Math.round(140 * scale);
|
||||||
let innerWidth = props.width - mLeft - mRight;
|
let innerWidth = props.width - mLeft - mRight;
|
||||||
|
|
||||||
this._updateScales(innerWidth, this.state.maxPwr, props.available);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
barHeight,
|
barHeight,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
@@ -141,41 +131,67 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.setState({ dep: Object.assign({}, dep) });
|
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
|
* Update state based on property and context changes
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
* @param {Object} nextProps Incoming/Next properties
|
||||||
* @param {Object} nextContext Incoming/Next context
|
* @param {Object} nextContext Incoming/Next context
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
let { innerWidth, maxPwr } = this.state;
|
|
||||||
let { language, sizeRatio } = this.context;
|
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) {
|
if (language !== nextContext.language) {
|
||||||
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
||||||
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
||||||
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
|
|
||||||
this.setState({ maxPwr: nextMaxPwr });
|
|
||||||
} else if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
|
||||||
this._updateDimensions(nextProps, 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
|
* Render the power bands
|
||||||
* @return {React.Component} Power bands
|
* @return {React.Component} Power bands
|
||||||
@@ -185,78 +201,27 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { wattScale, pctScale, context, props, state } = this;
|
let { pctScale, context, props, state } = this;
|
||||||
let { translate, formats } = context.language;
|
let { translate, formats } = context.language;
|
||||||
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
||||||
let { available, bands } = props;
|
let { ship } = props;
|
||||||
let { innerWidth, ret, dep } = state;
|
let { innerWidth, ret, dep, barHeight } = state;
|
||||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
|
|
||||||
let deployed = [];
|
let {
|
||||||
let retracted = [];
|
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 retSelected = Object.keys(ret).length > 0;
|
||||||
let depSelected = Object.keys(dep).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 (
|
return (
|
||||||
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
<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} />
|
<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.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='-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.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, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,24 +3,49 @@ import PropTypes from 'prop-types';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import PowerBands from './PowerBands';
|
import PowerBands from './PowerBands';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Power, NoPower } from './SvgIcons';
|
import { Power, NoPower } from './SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { Ship, Module } from 'ed-forge';
|
||||||
|
|
||||||
const POWER = [
|
/**
|
||||||
null,
|
* Makes a comparison based on the order `false < undefined < true` (fut) and
|
||||||
null,
|
* maps it to `[-1, 0, 1]`.
|
||||||
<NoPower className='icon warning' />,
|
* @param {boolean} a Bool or undefined
|
||||||
<Power className='secondary-disabled' />
|
* @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
|
* Power Management Section
|
||||||
*/
|
*/
|
||||||
export default class PowerManagement extends TranslatedComponent {
|
export default class PowerManagement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,19 +54,17 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._renderPowerRows = this._renderPowerRows.bind(this);
|
autoBind(this);
|
||||||
this._updateWidth = this._updateWidth.bind(this);
|
|
||||||
this._sort = this._sort.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
predicate: 'pwr',
|
predicate: 'pwr',
|
||||||
desc: false,
|
desc: true,
|
||||||
width: 0
|
width: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sort order and sort
|
* Set the sort order
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
@@ -53,50 +76,51 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
desc = true;
|
desc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sort(this.props.ship, predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the power list
|
* Sorts the power list
|
||||||
* @param {Ship} ship Ship instance
|
* @param {Module[]} modules Modules to sort
|
||||||
* @param {string} predicate Sort predicate
|
* @returns {Module[]} Sorted modules
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
*/
|
||||||
_sort(ship, predicate, desc) {
|
_sortAndFilter(modules) {
|
||||||
let powerList = ship.powerList;
|
modules = modules.filter((m) => m.get('powerdraw') >= 0);
|
||||||
let comp = slotComparator.bind(null, this.context.language.translate);
|
let { translate } = this.context.language;
|
||||||
|
const { predicate, desc } = this.state;
|
||||||
|
let comp;
|
||||||
switch (predicate) {
|
switch (predicate) {
|
||||||
case 'n': comp = comp(null, desc); break;
|
case 'n': comp = (a, b) => translate(a.readMeta('type')).localeCompare(
|
||||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
translate(b.readMeta('type'))
|
||||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
); break;
|
||||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
// case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
case 'pri': comp = (a, b) => a.getPowerPriority() - b.getPowerPriority(); break;
|
||||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); 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;
|
||||||
}
|
}
|
||||||
|
modules.sort(comp);
|
||||||
powerList.sort(comp);
|
if (desc) {
|
||||||
|
modules.reverse();
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update slot priority
|
* Creates a callback that changes the power priority for the given module
|
||||||
* @param {Object} slot Slot model
|
* based on the given delta.
|
||||||
* @param {number} inc increment / decrement
|
* @param {Module} m Module to set the priority for
|
||||||
|
* @param {Number} delta Delta to set
|
||||||
|
* @returns {Function} Callback
|
||||||
*/
|
*/
|
||||||
_priority(slot, inc) {
|
_prioCb(m, delta) {
|
||||||
if (this.props.ship.setSlotPriority(slot, slot.priority + inc)) {
|
return () => {
|
||||||
this.props.onChange();
|
const prio = m.getPowerPriority();
|
||||||
|
const newPrio = Math.max(0, prio + delta);
|
||||||
|
if (0 <= newPrio) {
|
||||||
|
m.setPowerPriority(newPrio);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle slot active/inactive
|
|
||||||
* @param {Object} slot Slot model
|
|
||||||
*/
|
|
||||||
_toggleEnabled(slot) {
|
|
||||||
this.props.ship.setSlotEnabled(slot, !slot.enabled);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,37 +134,36 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
_renderPowerRows(ship, translate, pwr, pct) {
|
_renderPowerRows(ship, translate, pwr, pct) {
|
||||||
let powerRows = [];
|
let powerRows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
let modules = this._sortAndFilter(ship.getModules());
|
||||||
let slot = ship.powerList[i];
|
for (let m of modules) {
|
||||||
|
|
||||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
|
||||||
let m = slot.m;
|
|
||||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
|
||||||
let retractedElem = null, deployedElem = null;
|
let retractedElem = null, deployedElem = null;
|
||||||
|
const flipEnabled = () => m.setEnabled();
|
||||||
if (slot.enabled) {
|
if (m.isEnabled()) {
|
||||||
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
|
let powered = m.isPowered();
|
||||||
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
|
retractedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.retracted)}</td>;
|
||||||
|
deployedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.deployed)}</td>;
|
||||||
} else {
|
} else {
|
||||||
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
|
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={flipEnabled}>{translate('disabled')}</td>;
|
||||||
}
|
}
|
||||||
|
|
||||||
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
|
const slot = m.getSlot();
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
|
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
|
||||||
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
|
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
|
||||||
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></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>
|
<td>
|
||||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>►</span>
|
||||||
{' ' + (slot.priority + 1) + ' '}
|
{' ' + (m.getPowerPriority() + 1) + ' '}
|
||||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
<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>
|
</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}
|
{retractedElem}
|
||||||
{deployedElem}
|
{deployedElem}
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return powerRows;
|
return powerRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +178,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
* Add listeners when about to mount and sort power list
|
* Add listeners when about to mount and sort power list
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._sort(this.props.ship, this.state.predicate, this.state.desc);
|
|
||||||
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,17 +188,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
this._updateWidth();
|
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
|
* Remove listeners on unmount
|
||||||
*/
|
*/
|
||||||
@@ -191,39 +202,38 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let { ship, code } = this.props;
|
let { ship, code } = this.props;
|
||||||
let { translate, formats } = this.context.language;
|
let { translate, formats } = this.context.language;
|
||||||
let pwr = formats.f2;
|
let pp = ship.getPowerPlant();
|
||||||
let pp = ship.standard[0].m;
|
|
||||||
let sortOrder = this._sortOrder;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortOrder('n')} >{translate('module')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
|
{/* <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('t')} >{translate('type')}</th> */}
|
||||||
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
|
<th style={{ width: '4em' }} className='sortable' onClick={() => this._sortOrder('pri')} >{translate('pri')}</th>
|
||||||
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
|
<th colSpan='2' className='sortable' onClick={() => this._sortOrder('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={() => this._sortOrder('r')} >{translate('ret')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('d')} >{translate('dep')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{pp.class + pp.rating}</td>
|
<td>{String(pp.getClass()) + pp.getRating()}</td>
|
||||||
<td className='le shorten cap' >{translate('pp')}</td>
|
<td className='le shorten cap' >{translate('pp')}</td>
|
||||||
<td><u >{translate('SYS')}</u></td>
|
|
||||||
<td>1</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 className='ri'><u>100%</u></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td style={{ lineHeight:0 }} colSpan='8'><hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} /></td></tr>
|
<tr><td style={{ lineHeight:0 }} colSpan='8'>
|
||||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
<hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} />
|
||||||
|
</td></tr>
|
||||||
|
{this._renderPowerRows(ship, translate, formats.f2, formats.pct1)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
<PowerBands width={this.state.width} ship={ship} code={code} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { Rocket } from './SvgIcons';
|
import { Rocket } from './SvgIcons';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Factory, Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship picker
|
* Ship picker
|
||||||
@@ -14,40 +15,49 @@ import cn from 'classnames';
|
|||||||
export default class ShipPicker extends TranslatedComponent {
|
export default class ShipPicker extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
ship: PropTypes.string.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
build: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
ship: 'eagle'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor
|
* constructor
|
||||||
* @param {object} props Properties react
|
* @param {object} props Properties react
|
||||||
* @param {object} context react context
|
* @param {object} context react context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) { // eslint-disable-line
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.shipOrder = Object.keys(Ships).sort();
|
this.state = {
|
||||||
this._toggleMenu = this._toggleMenu.bind(this);
|
menuOpen: false,
|
||||||
this._closeMenu = this._closeMenu.bind(this);
|
opponent: {
|
||||||
|
self: true,
|
||||||
this.state = { menuOpen: false };
|
type: props.ship.getShipType(),
|
||||||
|
stock: false,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update ship
|
* Update ship
|
||||||
* @param {object} ship the ship
|
* @param {boolean} self True to compare with ship itself
|
||||||
* @param {string} build the build, if present
|
* @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) {
|
_shipChange(self, type, stock = false, id = null) {
|
||||||
this._closeMenu();
|
const opponent = { self, type, stock, id };
|
||||||
|
if (isEqual(opponent, this.state.opponent)) {
|
||||||
// Ensure that the ship has changed
|
this.setState({ menuOpen: false });
|
||||||
if (ship !== this.props.ship || build !== this.props.build) {
|
} else {
|
||||||
this.props.onChange(ship, build);
|
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
|
* @returns {object} the picker menu
|
||||||
*/
|
*/
|
||||||
_renderPickerMenu() {
|
_renderPickerMenu() {
|
||||||
const { ship, build } = this.props;
|
const { menuOpen } = this.state;
|
||||||
const _shipChange = this._shipChange;
|
if (!menuOpen) {
|
||||||
const builds = Persist.getBuilds();
|
return null;
|
||||||
const buildList = [];
|
|
||||||
for (let shipId of this.shipOrder) {
|
|
||||||
const shipBuilds = [];
|
|
||||||
// Add stock build
|
|
||||||
const stockSelected = (ship == shipId && !build);
|
|
||||||
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
|
|
||||||
if (builds[shipId]) {
|
|
||||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
|
||||||
for (let buildName of buildNameOrder) {
|
|
||||||
const buildSelected = ship === shipId && build === buildName;
|
|
||||||
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildList;
|
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,41 +111,36 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
this.setState({ menuOpen: !menuOpen });
|
this.setState({ menuOpen: !menuOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the menu
|
|
||||||
*/
|
|
||||||
_closeMenu() {
|
|
||||||
const { menuOpen } = this.state;
|
|
||||||
if (menuOpen) {
|
|
||||||
this._toggleMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render picker
|
* Render picker
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { translate } = this.context.language;
|
||||||
const { formats, translate, units } = language;
|
const { ship } = this.props;
|
||||||
const { ship, build } = this.props;
|
|
||||||
const { menuOpen } = this.state;
|
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 (
|
return (
|
||||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||||
<div className='menu'>
|
<div className='menu'>
|
||||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||||
<span><Rocket className='warning' /></span>
|
<span><Rocket className='warning' /></span>
|
||||||
<span className='menu-item-label'>{shipString}</span>
|
<span className='menu-item-label'>
|
||||||
|
{`${translate(type)}: ${label}`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ menuOpen ?
|
|
||||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
|
||||||
<div className='quad'>
|
|
||||||
{this._renderPickerMenu()}
|
{this._renderPickerMenu()}
|
||||||
</div>
|
</div>
|
||||||
</div> : null }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,91 @@
|
|||||||
|
import autoBind from 'auto-bind';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Warning } from './SvgIcons';
|
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
|
* Ship Summary Table / Stats
|
||||||
*/
|
*/
|
||||||
export default class ShipSummaryTable extends TranslatedComponent {
|
export default class ShipSummaryTable extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ShipSummaryTable constructor
|
||||||
|
* @param {Object} props The props
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
|
this.state = {
|
||||||
|
shieldColour: 'blue'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the table
|
* Render the table
|
||||||
* @return {React.Component} Summary table
|
* @return {React.Component} Summary table
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, cargo, fuel } = this.props;
|
const { ship } = this.props;
|
||||||
let { language, tooltip, termtip } = this.context;
|
let { language, tooltip, termtip } = this.context;
|
||||||
let translate = language.translate;
|
let translate = language.translate;
|
||||||
let u = language.units;
|
let u = language.units;
|
||||||
let formats = language.formats;
|
let formats = language.formats;
|
||||||
let { time, int, round, f1, f2 } = formats;
|
let { time, int, f1, f2 } = formats;
|
||||||
let hide = tooltip.bind(null, null);
|
let hide = tooltip.bind(null, null);
|
||||||
|
|
||||||
const shieldGenerator = ship.findInternalByGroup('sg');
|
const speed = ship.get(SPEED);
|
||||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
const shipBoost = ship.get(BOOST_SPEED);
|
||||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
const boostInterval = ship.get(BOOST_INTERVAL);
|
||||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
const canThrust = 0 < speed;
|
||||||
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
|
const canBoost = canThrust && !isNaN(shipBoost);
|
||||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||||
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
|
|
||||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||||
|
|
||||||
|
const sgMetrics = 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'>
|
return <div id='summary'>
|
||||||
<table id='summaryTable'>
|
<div style={{ display: 'table', width: '100%' }}>
|
||||||
|
<div style={{ display: 'table-row' }}>
|
||||||
|
<table className={'summaryTable'}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
<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 rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||||
<th colSpan={5}>{translate('jump range')}</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('shield')}</th>
|
||||||
<th rowSpan={2}>{translate('integrity')}</th>
|
<th rowSpan={2}>{translate('integrity')}</th>
|
||||||
<th rowSpan={2}>{translate('DPS')}</th>
|
<th rowSpan={2}>{translate('DPS')}</th>
|
||||||
@@ -53,15 +93,18 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
<th rowSpan={2}>{translate('TTD')}</th>
|
<th rowSpan={2}>{translate('TTD')}</th>
|
||||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||||
<th rowSpan={2}>{translate('cargo')}</th>
|
<th rowSpan={2}>{translate('cargo')}</th>
|
||||||
<th rowSpan={2}>{translate('pax')}</th>
|
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{translate('pax')}</th>
|
||||||
<th rowSpan={2}>{translate('fuel')}</th>
|
<th rowSpan={2}>{translate('fuel')}</th>
|
||||||
<th colSpan={3}>{translate('mass')}</th>
|
<th colSpan={3}>{translate('mass')}</th>
|
||||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||||
<th rowSpan={2}>{translate('crew')}</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, '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>
|
||||||
<tr>
|
<tr>
|
||||||
<th className='lft'>{translate('max')}</th>
|
<th className="lft">{translate('max')}</th>
|
||||||
<th>{translate('unladen')}</th>
|
<th>{translate('unladen')}</th>
|
||||||
<th>{translate('laden')}</th>
|
<th>{translate('laden')}</th>
|
||||||
<th>{translate('total unladen')}</th>
|
<th>{translate('total unladen')}</th>
|
||||||
@@ -73,31 +116,152 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })}
|
||||||
<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>
|
onMouseLeave={hide}
|
||||||
<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>
|
>{canThrust ?
|
||||||
<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>
|
<span>{int(speed)}{u['m/s']}</span> :
|
||||||
<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>
|
<span className='warning'>0<Warning/></span>
|
||||||
<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>
|
||||||
<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 onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
|
||||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
>{canBoost ?
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
<span>{int(shipBoost)}{u['m/s']}</span> :
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
}</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>{f1(ship.totalHps)}</td> */}
|
||||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
<td>{ship.get(CARGO_CAPACITY)}{u.T}</td>
|
||||||
<td>{ship.passengerCapacity}</td>
|
<td>{ship.get(PASSENGER_CAPACITY)}</td>
|
||||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
<td>{ship.get(FUEL_CAPACITY)}{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_HULL_MASS', { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
>{ship.readProp('hullmass')}{u.T}</td>
|
||||||
<td>{int(ship.hardness)}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
|
||||||
<td>{ship.crew}</td>
|
onMouseLeave={hide}
|
||||||
<td>{ship.masslock}</td>
|
>{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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN_LR = 8; // Left/ Right margin
|
const MARGIN_LR = 8; // Left/ Right margin
|
||||||
|
|
||||||
@@ -7,7 +8,6 @@ const MARGIN_LR = 8; // Left/ Right margin
|
|||||||
* Horizontal Slider
|
* Horizontal Slider
|
||||||
*/
|
*/
|
||||||
export default class Slider extends React.Component {
|
export default class Slider extends React.Component {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
axis: false,
|
axis: false,
|
||||||
min: 0,
|
min: 0,
|
||||||
@@ -17,12 +17,12 @@ export default class Slider extends React.Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
axis: PropTypes.bool,
|
axis: PropTypes.bool,
|
||||||
axisUnit: PropTypes.string,
|
axisUnit: PropTypes.string,// units (T, M, etc.)
|
||||||
max: PropTypes.number,
|
max: PropTypes.number,
|
||||||
min: PropTypes.number,
|
min: PropTypes.number,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,// function which determins percent value
|
||||||
onResize: PropTypes.func,
|
onResize: PropTypes.func,
|
||||||
percent: PropTypes.number.isRequired,
|
percent: PropTypes.number.isRequired,// value of slider
|
||||||
scale: PropTypes.number
|
scale: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,11 +32,7 @@ export default class Slider extends React.Component {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._down = this._down.bind(this);
|
autoBind(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 };
|
this.state = { width: 0 };
|
||||||
}
|
}
|
||||||
@@ -136,31 +132,29 @@ export default class Slider extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
let outerWidth = this.state.outerWidth;
|
let outerWidth = this.state.outerWidth;
|
||||||
let { axis, axisUnit, min, max, scale } = this.props;
|
let { axis, axisUnit, min, max, scale } = this.props;
|
||||||
|
|
||||||
let style = {
|
let style = {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: axis ? '2.5em' : '1.5em',
|
height: axis ? '2.5em' : '1.5em',
|
||||||
boxSizing: 'border-box'
|
boxSizing: 'border-box'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!outerWidth) {
|
if (!outerWidth) {
|
||||||
return <svg style={style} ref={node => this.node = node} />;
|
return <svg style={style} ref={node => this.node = node} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let margin = MARGIN_LR * scale;
|
let margin = MARGIN_LR * scale;
|
||||||
let width = outerWidth - (margin * 2);
|
let width = outerWidth - (margin * 2);
|
||||||
let pctPos = width * this.props.percent;
|
let pctPos = width * this.props.percent;
|
||||||
|
return <div><svg
|
||||||
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
|
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' 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' />
|
<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} />
|
<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} />
|
<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' }}>
|
{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={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='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||||
</g>}
|
</g>}
|
||||||
</svg>;
|
</svg>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,31 +2,37 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { ListModifications, Modified } from './SvgIcons';
|
||||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||||
import ModificationsMenu from './ModificationsMenu';
|
import ModificationsMenu from './ModificationsMenu';
|
||||||
import { diffDetails } from '../utils/SlotFunctions';
|
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
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
|
* Abstract Slot
|
||||||
*/
|
*/
|
||||||
export default class Slot extends TranslatedComponent {
|
export default class Slot extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
availableModules: PropTypes.func.isRequired,
|
currentMenu: PropTypes.any,
|
||||||
onSelect: PropTypes.func.isRequired,
|
hideSearch: PropTypes.bool,
|
||||||
onOpen: PropTypes.func.isRequired,
|
m: PropTypes.instanceOf(Module),
|
||||||
maxClass: PropTypes.number.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
m: PropTypes.object,
|
|
||||||
enabled: PropTypes.bool.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
eligible: PropTypes.object,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
drag: PropTypes.func,
|
drag: PropTypes.func,
|
||||||
drop: PropTypes.func,
|
drop: PropTypes.func,
|
||||||
dropClass: PropTypes.string
|
dropClass: PropTypes.string,
|
||||||
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,23 +41,122 @@ export default class Slot extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._modificationsSelected = false;
|
this.state = { menuIndex: 0 };
|
||||||
|
|
||||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
|
||||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
* Generate the slot contents
|
||||||
* as necessary.
|
* @return {React.Component} Slot contents
|
||||||
* @return {string} CSS Class name
|
|
||||||
*/
|
*/
|
||||||
_getClassNames() {
|
_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;
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +165,19 @@ export default class Slot extends TranslatedComponent {
|
|||||||
* @return {string} label
|
* @return {string} label
|
||||||
*/
|
*/
|
||||||
_getMaxClassLabel() {
|
_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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,7 +187,13 @@ export default class Slot extends TranslatedComponent {
|
|||||||
_contextMenu(event) {
|
_contextMenu(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -80,61 +203,41 @@ export default class Slot extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let language = this.context.language;
|
let language = this.context.language;
|
||||||
let translate = language.translate;
|
let translate = language.translate;
|
||||||
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
let {
|
||||||
let slotDetails, modificationsMarker, menu;
|
currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
|
||||||
|
onPropToggle,
|
||||||
if (!selected) {
|
} = this.props;
|
||||||
// If not selected then sure that modifications flag is unset
|
const { menuIndex } = this.state;
|
||||||
this._modificationsSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
|
|
||||||
modificationsMarker = JSON.stringify(m);
|
|
||||||
} else {
|
|
||||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
|
||||||
modificationsMarker = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
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)}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement touch dragging
|
// TODO: implement touch dragging
|
||||||
|
const selected = currentMenu === m.getSlot();
|
||||||
return (
|
return (
|
||||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
<div
|
||||||
<div className='details-container'>
|
className={cn('slot', dropClass, { selected })}
|
||||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
onContextMenu={this._contextMenu}
|
||||||
{slotDetails}
|
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>
|
</div>
|
||||||
{menu}
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the modifications flag when selecting the modifications icon
|
|
||||||
*/
|
|
||||||
_toggleModifications() {
|
|
||||||
this._modificationsSelected = !this._modificationsSelected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,44 +2,35 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
import { canMount } from '../utils/SlotFunctions';
|
|
||||||
import { Equalizer } from '../components/SvgIcons';
|
import { Equalizer } from '../components/SvgIcons';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
const browser = require('detect-browser');
|
const browser = require('detect-browser');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Slot Section
|
* Abstract Slot Section
|
||||||
*/
|
*/
|
||||||
export default class SlotSection extends TranslatedComponent {
|
export default class SlotSection extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship),
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onCargoChange: PropTypes.func.isRequired,
|
|
||||||
onFuelChange: PropTypes.func.isRequired,
|
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
togglePwr: PropTypes.func
|
togglePwr: PropTypes.func,
|
||||||
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
* @param {string} sectionId Section DOM Id
|
|
||||||
* @param {string} sectionName Section name
|
* @param {string} sectionName Section name
|
||||||
*/
|
*/
|
||||||
constructor(props, context, sectionId, sectionName) {
|
constructor(props, sectionName) {
|
||||||
super(props);
|
super(props);
|
||||||
this.sectionId = sectionId;
|
autoBind(this);
|
||||||
|
|
||||||
this.sectionName = sectionName;
|
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 = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,31 +38,7 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
// _getSlots()
|
// _getSlots()
|
||||||
// _getSectionMenu()
|
// _getSectionMenu()
|
||||||
// _contextMenu()
|
// _contextMenu()
|
||||||
|
// componentDidUpdate(prevProps)
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot Drag Handler
|
* Slot Drag Handler
|
||||||
@@ -137,10 +104,18 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||||
const mCopy = m.clone();
|
const mCopy = m.clone();
|
||||||
this.props.ship.use(targetSlot, mCopy, false);
|
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
|
// Copy power info
|
||||||
targetSlot.enabled = originSlot.enabled;
|
targetSlot.enabled = originSlot.enabled;
|
||||||
targetSlot.priority = originSlot.priority;
|
targetSlot.priority = originSlot.priority;
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Store power info
|
// Store power info
|
||||||
@@ -169,7 +144,18 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
targetSlot.enabled = targetEnabled;
|
targetSlot.enabled = targetEnabled;
|
||||||
targetSlot.priority = targetPriority;
|
targetSlot.priority = targetPriority;
|
||||||
}
|
}
|
||||||
this.props.onChange();
|
this.props.ship
|
||||||
|
.updatePowerGenerated()
|
||||||
|
.updatePowerUsed()
|
||||||
|
.recalculateMass()
|
||||||
|
.updateJumpStats()
|
||||||
|
.recalculateShield()
|
||||||
|
.recalculateShieldCells()
|
||||||
|
.recalculateArmour()
|
||||||
|
.recalculateDps()
|
||||||
|
.recalculateEps()
|
||||||
|
.recalculateHps()
|
||||||
|
.updateMovement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +189,17 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
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
|
* Close current menu
|
||||||
*/
|
*/
|
||||||
@@ -219,14 +216,13 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
||||||
let open = this._openMenu.bind(this, this.sectionName);
|
|
||||||
let ctx = wrapCtxMenu(this._contextMenu);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
<div className="group" onDragLeave={this._dragOverNone}>
|
||||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
<div className={cn('section-menu', { selected: sectionMenuOpened })}
|
||||||
<h1>{translate(this.sectionName)} <Equalizer/></h1>
|
onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
|
||||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
<h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
|
||||||
|
{sectionMenuOpened && this._getSectionMenu()}
|
||||||
</div>
|
</div>
|
||||||
{this._getSlots()}
|
{this._getSlots()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,139 +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;
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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), disabled: m.grp !== 'bh' && !slot.enabled })}>
|
|
||||||
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
|
|
||||||
<div>
|
|
||||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
|
|
||||||
<div className={'r'}>{formats.round(mass)}{units.T}</div>
|
|
||||||
<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,114 +1,67 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import StandardSlot from './StandardSlot';
|
import Slot from './Slot';
|
||||||
import Module from '../shipyard/Module';
|
import autoBind from 'auto-bind';
|
||||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
|
||||||
import { stopCtxPropagation } 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
|
* Standard Slot section
|
||||||
*/
|
*/
|
||||||
export default class StandardSlotSection extends SlotSection {
|
export default class StandardSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
* @param {Object} context React Component context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'standard', 'core internal');
|
super(props, 'core internal');
|
||||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
autoBind(this);
|
||||||
this._selectBulkhead = this._selectBulkhead.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the lightest/optimal available standard modules
|
* Resets all modules of the ship
|
||||||
*/
|
*/
|
||||||
_optimizeStandard() {
|
_emptyAll() {
|
||||||
this.props.ship.useLightestStandard();
|
this.props.ship.getModules().forEach((slot) => slot.reset());
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill all standard slots with the specificed rating (using max class)
|
* Sets all modules to a specific rating
|
||||||
* @param {Boolean} shielded True if shield generator should be included
|
* @param {string} rating Module rating to set
|
||||||
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
* @param {string} fsdPPException Custom rating for FSD
|
||||||
*/
|
*/
|
||||||
_multiPurpose(shielded, bulkheadIndex) {
|
_nRated(rating, fsdPPException) {
|
||||||
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
|
const { ship } = this.props;
|
||||||
this.props.onChange();
|
const pp = ship.getPowerPlant();
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
pp.setItem('powerplant', pp.getSize(), fsdPPException || rating);
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
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();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trader Build
|
* Creates a new slot for a given module.
|
||||||
* @param {Boolean} shielded True if shield generator should be included
|
* @param {Module} m Module to create the slot for
|
||||||
|
* @param {function} warning Warning callback
|
||||||
|
* @return {React.Component} Slot component
|
||||||
*/
|
*/
|
||||||
_optimizeCargo(shielded) {
|
_mkSlot(m, warning) {
|
||||||
ShipRoles.trader(this.props.ship, shielded);
|
const { currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
this.props.onChange();
|
return <Slot key={m.getSlot()} m={m} warning={warning} hideSearch={true}
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
currentMenu={currentMenu} propsToShow={propsToShow} onPropToggle={onPropToggle}
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
/>;
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Miner Build
|
|
||||||
* @param {Boolean} shielded True if shield generator should be included
|
|
||||||
*/
|
|
||||||
_optimizeMiner(shielded) {
|
|
||||||
ShipRoles.miner(this.props.ship, shielded);
|
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Racer role
|
|
||||||
*/
|
|
||||||
_optimizeRacer() {
|
|
||||||
ShipRoles.racer(this.props.ship);
|
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,108 +69,30 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu, cargo, fuel } = this.props;
|
const { ship } = this.props;
|
||||||
let slots = new Array(8);
|
const fsd = ship.getFSD();
|
||||||
let open = this._openMenu;
|
return [
|
||||||
let select = this._selectModule;
|
this._mkSlot(ship.getAlloys()),
|
||||||
let st = ship.standard;
|
this._mkSlot(
|
||||||
let avail = ship.getAvailableModules().standard;
|
ship.getPowerPlant(),
|
||||||
let bh = ship.bulkheads;
|
(m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
|
||||||
|
),
|
||||||
slots[0] = <StandardSlot
|
this._mkSlot(
|
||||||
key='bh'
|
ship.getThrusters(),
|
||||||
slot={bh}
|
(m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
|
||||||
modules={ship.getAvailableModules().bulkheads}
|
),
|
||||||
onOpen={open.bind(this, bh)}
|
this._mkSlot(fsd),
|
||||||
onSelect={this._selectBulkhead}
|
this._mkSlot(
|
||||||
selected={currentMenu == bh}
|
ship.getPowerDistributor(),
|
||||||
onChange={this.props.onChange}
|
(m) => moduleGet(m, 'enginescapacity') <= ship.readProp('boostenergy'),
|
||||||
ship={ship}
|
),
|
||||||
/>;
|
this._mkSlot(ship.getLifeSupport()),
|
||||||
|
this._mkSlot(ship.getSensors()),
|
||||||
slots[1] = <StandardSlot
|
this._mkSlot(
|
||||||
key='pp'
|
ship.getCoreFuelTank(),
|
||||||
slot={st[0]}
|
(m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,23 +100,18 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
let planetaryDisabled = this.props.ship.internal.length < 4;
|
const { translate } = this.context.language;
|
||||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
|
<li className='lc' tabIndex="0" onClick={this._emptyAll}>{translate('empty all slots')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('roles')}</div>
|
<div className='select-group cap'>{translate('core')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
|
<li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '5', undefined)}>{translate('A-rated')}</li>
|
||||||
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
|
<li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', undefined)}>{translate('D-rated')}</li>
|
||||||
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
|
<li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', '5')}>{translate('D-rated + A-rated FSD/PP')}</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, true)}>{translate('Miner')}</li>
|
|
||||||
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</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)
|
* Shopping icon (dollar sign)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Document Root Tooltip
|
* Document Root Tooltip
|
||||||
*/
|
*/
|
||||||
export default class Tooltip extends TranslatedComponent {
|
export default class Tooltip extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
rect: PropTypes.object.isRequired,
|
rect: PropTypes.object.isRequired,
|
||||||
options: PropTypes.object
|
options: PropTypes.object
|
||||||
@@ -127,5 +126,4 @@ export default class Tooltip extends TranslatedComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Abstract Translated Component
|
* Abstract Translated Component
|
||||||
*/
|
*/
|
||||||
export default class TranslatedComponent extends React.Component {
|
export default class TranslatedComponent extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
language: PropTypes.object.isRequired,
|
language: PropTypes.object.isRequired,
|
||||||
sizeRatio: PropTypes.number.isRequired,
|
sizeRatio: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -1,51 +1,44 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility Slot Section
|
* Utility Slot Section
|
||||||
*/
|
*/
|
||||||
export default class UtilitySlotSection extends SlotSection {
|
export default class UtilitySlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'utility', 'utility mounts');
|
super(props, 'utility mounts');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all utility slots and close the menu
|
* Empty all utility slots and close the menu
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.props.ship.emptyUtility();
|
this.props.ship.getUtilities().forEach((slot) => slot.reset());
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount module in utility slot, replace all if Alt is held
|
* 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} rating Module Rating
|
||||||
* @param {string} name Module name
|
|
||||||
* @param {Synthetic} event Event
|
* @param {Synthetic} event Event
|
||||||
*/
|
*/
|
||||||
_use(group, rating, name, event) {
|
_use(type, rating, event) {
|
||||||
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
|
const fillAll = event.getModifierState('Alt');
|
||||||
this.props.onChange();
|
for (const slot of this.props.ship.getUtilities(undefined, true)) {
|
||||||
this._close();
|
if (slot.isEmpty() || fillAll) {
|
||||||
|
slot.setItem(type, '', rating);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
this._close();
|
||||||
* Empty all utility slots on right-click
|
|
||||||
*/
|
|
||||||
_contextMenu() {
|
|
||||||
this._empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,32 +47,23 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getUtilities(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass === 0) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
currentMenu={currentMenu}
|
||||||
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)}
|
drag={this._drag.bind(this, h)}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
ship={ship}
|
m={h}
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
enabled={h.enabled ? true : false}
|
||||||
|
propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -89,35 +73,35 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _use = this._use;
|
let _use = this._use;
|
||||||
|
|
||||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
|
||||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('sb')}</div>
|
<div className='select-group cap'>{translate('sb')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '5')}>A</li>
|
||||||
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '4')}>B</li>
|
||||||
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '3')}>C</li>
|
||||||
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '2')}>D</li>
|
||||||
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '1')}>E</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('hs')}</div>
|
<div className='select-group cap'>{translate('hs')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'heatsinklauncher', '')}>{translate('Heat Sink Launcher')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('ch')}</div>
|
<div className='select-group cap'>{translate('ch')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'chafflauncher', '')}>{translate('Chaff Launcher')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('po')}</div>
|
<div className='select-group cap'>{translate('po')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'pointdefence', '')}>{translate('Point Defence')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import React, { PropTypes } from 'react';
|
import React, { PropTypes } from 'react';
|
||||||
import Measure from 'react-measure';
|
import ContainerDimensions from 'react-container-dimensions';
|
||||||
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts';
|
||||||
|
|
||||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||||
const LABEL_COLOUR = '#000000';
|
const LABEL_COLOUR = '#000000';
|
||||||
@@ -17,7 +17,6 @@ const merge = function(one, two) {
|
|||||||
* A vertical bar chart
|
* A vertical bar chart
|
||||||
*/
|
*/
|
||||||
export default class VerticalBarChart extends TranslatedComponent {
|
export default class VerticalBarChart extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
data : PropTypes.array.isRequired,
|
data : PropTypes.array.isRequired,
|
||||||
yMax : PropTypes.number
|
yMax : PropTypes.number
|
||||||
@@ -32,13 +31,6 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._termtip = this._termtip.bind(this);
|
this._termtip = this._termtip.bind(this);
|
||||||
|
|
||||||
this.state = {
|
|
||||||
dimensions: {
|
|
||||||
width: 300,
|
|
||||||
height: 300
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +38,6 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
* @returns {Object} the markup
|
* @returns {Object} the markup
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { width, height } = this.state.dimensions;
|
|
||||||
const { tooltip, termtip } = this.context;
|
const { tooltip, termtip } = this.context;
|
||||||
|
|
||||||
// Calculate maximum for Y
|
// Calculate maximum for Y
|
||||||
@@ -56,15 +47,19 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
const localMax = Math.max(dataMax, yMax);
|
const localMax = Math.max(dataMax, yMax);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
|
<ContainerDimensions>
|
||||||
|
{ ({ width }) => (
|
||||||
<div width='100%'>
|
<div width='100%'>
|
||||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
<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' />
|
<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]}/>
|
<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)}/>
|
<Bar dataKey='value' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
<LabelList dataKey='value' position='insideTop'/>
|
||||||
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</div>
|
</div>
|
||||||
</Measure>
|
)}
|
||||||
|
</ContainerDimensions>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,29 +78,3 @@ export default class VerticalBarChart extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,204 +1,104 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
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 LineChart from '../components/LineChart';
|
||||||
import Slider from '../components/Slider';
|
import { moduleReduce } from 'ed-forge/lib/src/helper';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import { chain, keys, mapValues, values } from 'lodash';
|
||||||
import Module from '../shipyard/Module';
|
|
||||||
|
|
||||||
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
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
|
* Weapon damage chart
|
||||||
*/
|
*/
|
||||||
export default class WeaponDamageChart extends TranslatedComponent {
|
export default class WeaponDamageChart extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponentDefence: PropTypes.object.isRequired,
|
||||||
hull: PropTypes.bool.isRequired,
|
|
||||||
engagementRange: PropTypes.number.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
|
* Render damage dealt
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { maxRange } = this.state;
|
const { code, ship, opponentDefence, engagementRange } = this.props;
|
||||||
const { ship, opponent, engagementRange } = this.props;
|
|
||||||
|
|
||||||
const sortOrder = this._sortOrder;
|
const hardpoints = ship.getHardpoints();
|
||||||
const onCollapseExpand = this._onCollapseExpand;
|
const hardpointsMap = chain(hardpoints)
|
||||||
|
.map((m) => [m.getSlot(), m])
|
||||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
.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 (
|
return (
|
||||||
<span>
|
<div>
|
||||||
<LineChart
|
<LineChart
|
||||||
xMax={maxRange}
|
xMin={0}
|
||||||
yMax={this.state.maxDps}
|
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')}
|
xLabel={translate('range')}
|
||||||
xUnit={translate('m')}
|
xUnit={translate('m')}
|
||||||
yLabel={translate('sdps')}
|
yLabel={translate('sustaineddamagepersecond')}
|
||||||
series={this.state.weaponNames}
|
series={hardpoints.map((m) => m.getSlot())}
|
||||||
xMark={this.props.engagementRange}
|
xMark={engagementRange}
|
||||||
colors={DAMAGE_DEALT_COLORS}
|
colors={DAMAGE_DEALT_COLORS}
|
||||||
func={this.state.calcSDpsFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as IT from './it';
|
|||||||
import * as RU from './ru';
|
import * as RU from './ru';
|
||||||
import * as PL from './pl';
|
import * as PL from './pl';
|
||||||
import * as PT from './pt';
|
import * as PT from './pt';
|
||||||
|
import * as CN from './cn';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
let fallbackTerms = EN.terms;
|
let fallbackTerms = EN.terms;
|
||||||
@@ -27,6 +28,7 @@ export function getLanguage(langCode) {
|
|||||||
case 'ru': lang = RU; break;
|
case 'ru': lang = RU; break;
|
||||||
case 'pl': lang = PL; break;
|
case 'pl': lang = PL; break;
|
||||||
case 'pt': lang = PT; break;
|
case 'pt': lang = PT; break;
|
||||||
|
case 'cn': lang = CN; break;
|
||||||
default:
|
default:
|
||||||
lang = EN;
|
lang = EN;
|
||||||
}
|
}
|
||||||
@@ -60,17 +62,20 @@ export function getLanguage(langCode) {
|
|||||||
},
|
},
|
||||||
translate,
|
translate,
|
||||||
units: {
|
units: {
|
||||||
|
ang: '°', // Angle
|
||||||
CR: <u>{translate('CR')}</u>, // Credits
|
CR: <u>{translate('CR')}</u>, // Credits
|
||||||
kg: <u>{translate('kg')}</u>, // Kilograms
|
kg: <u>{translate('kg')}</u>, // Kilograms
|
||||||
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
|
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
|
||||||
km: <u>{translate('km')}</u>, // Kilometers
|
km: <u>{translate('km')}</u>, // Kilometers
|
||||||
Ls: <u>{translate('Ls')}</u>, // Light Seconds
|
Ls: <u>{translate('Ls')}</u>, // Light Seconds
|
||||||
LY: <u>{translate('LY')}</u>, // Light Years
|
LY: <u>{translate('LY')}</u>, // Light Years
|
||||||
|
m: <u>{translate('m')}</u>, // Meters
|
||||||
MJ: <u>{translate('MJ')}</u>, // Mega Joules
|
MJ: <u>{translate('MJ')}</u>, // Mega Joules
|
||||||
'm/s': <u>{translate('m/s')}</u>, // Meters per second
|
'm/s': <u>{translate('m/s')}</u>, // Meters per second
|
||||||
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
|
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
|
||||||
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||||
mps: <u>{translate('m/s')}</u>, // Metres per second
|
mps: <u>{translate('m/s')}</u>, // Metres per second
|
||||||
|
pct: '%', // Percent
|
||||||
ps: <u>{translate('/s')}</u>, // per second
|
ps: <u>{translate('/s')}</u>, // per second
|
||||||
pm: <u>{translate('/min')}</u>, // per minute
|
pm: <u>{translate('/min')}</u>, // per minute
|
||||||
s: <u>{translate('secs')}</u>, // Seconds
|
s: <u>{translate('secs')}</u>, // Seconds
|
||||||
@@ -91,5 +96,6 @@ export const Languages = {
|
|||||||
fr: 'Français',
|
fr: 'Français',
|
||||||
ru: 'ру́сский',
|
ru: 'ру́сский',
|
||||||
pl: 'polski',
|
pl: 'polski',
|
||||||
pt: 'português'
|
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -59,7 +59,7 @@
|
|||||||
"empty all": "vide tout",
|
"empty all": "vide tout",
|
||||||
"Enter Name": "Entrer nom",
|
"Enter Name": "Entrer nom",
|
||||||
"Explorer": "explorateur",
|
"Explorer": "explorateur",
|
||||||
"fastest range": "gamme la plus rapide",
|
"farthest range": "gamme la plus rapide",
|
||||||
"fuel": "carburant",
|
"fuel": "carburant",
|
||||||
"fuel level": "niveau de carburant",
|
"fuel level": "niveau de carburant",
|
||||||
"full tank": "Réservoir plein",
|
"full tank": "Réservoir plein",
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ export const formats = {
|
|||||||
decimal: ',',
|
decimal: ',',
|
||||||
thousands: '.',
|
thousands: '.',
|
||||||
grouping: [3],
|
grouping: [3],
|
||||||
currency: ['', ' €'],
|
currency: ['$', ''],
|
||||||
dateTime: '%A, %e de %B de %Y, %X',
|
dateTime: '%A, %e de %B de %Y, %X',
|
||||||
date: '%d/%m/%Y',
|
date: '%d/%m/%Y',
|
||||||
time: '%H:%M:%S',
|
time: '%H:%M:%S',
|
||||||
periods: ['AM', 'PM'],
|
periods: ['AM', 'PM'],
|
||||||
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
|
days: ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'],
|
||||||
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
|
shortDays: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab'],
|
||||||
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
|
months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
|
||||||
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
|
shortMonths: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']
|
||||||
};
|
};
|
||||||
|
|
||||||
export { default as terms } from './pt.json';
|
export { default as terms } from './pt.json';
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
|
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
|
||||||
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
|
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
|
||||||
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
|
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
|
||||||
"PHRASE_LADEN": "Масса корабля с учётом топлива и грузов",
|
"PHRASE_LADEN": "Масса корабля с учетом топлива и грузов",
|
||||||
"PHRASE_NO_BUILDS": "Нечего сравнивать",
|
"PHRASE_NO_BUILDS": "Нечего сравнивать",
|
||||||
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
|
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
|
||||||
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
|
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
|
||||||
@@ -13,18 +13,21 @@
|
|||||||
"PHRASE_SG_RECOVER": "Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале",
|
"PHRASE_SG_RECOVER": "Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале",
|
||||||
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
|
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
|
||||||
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
|
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
|
||||||
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
|
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблем и целью",
|
||||||
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
|
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертеж",
|
||||||
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
|
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
|
||||||
|
"PHRASE_BLUEPRINT_FIFTY": "50% значения для чертежа",
|
||||||
|
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
|
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
|
||||||
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
|
"PHRASE_BLUEPRINT_RESET": "Сбросить все модификаторы и чертеж",
|
||||||
"PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект",
|
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
|
||||||
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
||||||
"PHRASE_SHOPPING_LIST": "Станции что продают эту сборку",
|
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
|
||||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули",
|
"PHRASE_SHOPPING_MATS": "Материалы которые нужны для сборки",
|
||||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители",
|
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
|
||||||
|
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесен в каждым типе, если используются все щитонакопители",
|
||||||
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
||||||
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
||||||
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
||||||
@@ -34,51 +37,59 @@
|
|||||||
"PHRASE_EFFECTIVE_ARMOUR": "Эффективная сила брони против разных типов урона",
|
"PHRASE_EFFECTIVE_ARMOUR": "Эффективная сила брони против разных типов урона",
|
||||||
"PHRASE_DAMAGE_TAKEN": "% общих повреждений полученных в разных типах урона",
|
"PHRASE_DAMAGE_TAKEN": "% общих повреждений полученных в разных типах урона",
|
||||||
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
|
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
|
||||||
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
|
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнезд",
|
||||||
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
|
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
|
||||||
|
"PHRASE_OVERALL_DAMAGE": "Разбивка источников устойчивого ДПС",
|
||||||
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
|
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
|
||||||
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
|
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
|
||||||
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
|
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
|
||||||
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
|
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Щелкните правой кновкой мыши чтобы объединить в группу.",
|
||||||
|
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнем из всех орудий",
|
||||||
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
||||||
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
|
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнем из всех орудий",
|
||||||
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
|
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУ за",
|
||||||
"TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУ при стрельбе из всех орудий",
|
||||||
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||||
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||||
"TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона",
|
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
|
||||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей",
|
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнезд на наборы для усиления модулей",
|
||||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнезд на наборы для усиления модулей",
|
||||||
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУ не пуст",
|
||||||
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
||||||
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУ не пуст",
|
||||||
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
|
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
|
||||||
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
|
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
|
||||||
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
|
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
|
||||||
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
|
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВГ",
|
||||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||||
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
|
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВГ",
|
||||||
|
"TT_SUMMARY_BOOST_INTERVAL": "Время заполнения с 4 пунктами в СИС",
|
||||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
||||||
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
||||||
|
"TT_SUMMARY_SHIELDS_SCB": "Прочность щита, включая бустеры и SCB",
|
||||||
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
|
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
|
||||||
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
|
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
|
||||||
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
|
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
|
||||||
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
|
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
|
||||||
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
|
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
|
||||||
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
||||||
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
|
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУ в секунду при стрельбе из всех орудий",
|
||||||
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
|
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУ при стрельбе из всех орудий и с 4 пунктами в ОРУ",
|
||||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок",
|
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
|
||||||
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
||||||
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
||||||
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||||
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||||
"HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||||
|
"PHRASE_FAIL_EDENGINEER": "Не удалось отправить в EDEngineer (запустите EDEngineer и убедитесь, что API запущен, затем обновите страницу).",
|
||||||
|
"PHRASE_FIREFOX_EDENGINEER": "Отправка в EDEngineer несовместима с настройками безопасности Firefox. Пожалуйста, попробуйте еще раз в Google Chrome.",
|
||||||
"am": "Блок Автом. Полевого Ремонта",
|
"am": "Блок Автом. Полевого Ремонта",
|
||||||
"bh": "Переборки",
|
"bh": "Переборки",
|
||||||
"bl": "Пучковый Лазер",
|
"bl": "Пучковый лазер",
|
||||||
"bsg": "Двухпоточный Щитогенератор",
|
"bsg": "Двухпоточный щитогенератор",
|
||||||
"c": "Орудие",
|
"c": "Пушка",
|
||||||
|
"causres": "Каустическое сопротивление",
|
||||||
|
"Caustic resistance": "Каустическое сопротивление",
|
||||||
"cc": "Контроллер магнитного снаряда для сбора",
|
"cc": "Контроллер магнитного снаряда для сбора",
|
||||||
"ch": "Разбрасыватель дипольных отражателей",
|
"ch": "Разбрасыватель дипольных отражателей",
|
||||||
"cr": "Грузовой стеллаж",
|
"cr": "Грузовой стеллаж",
|
||||||
@@ -89,7 +100,7 @@
|
|||||||
"fh": "Ангар для истребителя",
|
"fh": "Ангар для истребителя",
|
||||||
"fi": "FSD-перехватчик",
|
"fi": "FSD-перехватчик",
|
||||||
"fs": "Топливозаборник",
|
"fs": "Топливозаборник",
|
||||||
"fsd": "Рамочно Сместительный двигатель",
|
"fsd": "Рамочно-сместительный двигатель",
|
||||||
"ft": "Топливный бак",
|
"ft": "Топливный бак",
|
||||||
"fx": "Контроллер магнитного снаряда для топлива",
|
"fx": "Контроллер магнитного снаряда для топлива",
|
||||||
"hb": "Контроллер магнитного снаряда для взлома трюма",
|
"hb": "Контроллер магнитного снаряда для взлома трюма",
|
||||||
@@ -98,48 +109,81 @@
|
|||||||
"kw": "Сканер преступников",
|
"kw": "Сканер преступников",
|
||||||
"ls": "Система жизнеобеспечения",
|
"ls": "Система жизнеобеспечения",
|
||||||
"mc": "Многоствольное орудие",
|
"mc": "Многоствольное орудие",
|
||||||
|
"axmc": "Многоствольное орудие АИ",
|
||||||
"ml": "Проходочный лазер",
|
"ml": "Проходочный лазер",
|
||||||
"mr": "Ракетный лоток",
|
"mr": "Ракетный лоток",
|
||||||
|
"axmr": "Блок ракет АИ",
|
||||||
"mrp": "Набор для усиления модуля",
|
"mrp": "Набор для усиления модуля",
|
||||||
"nl": "Мины",
|
"nl": "Мины",
|
||||||
"pa": "Ускоритель плазмы",
|
"pa": "Ускоритель плазмы",
|
||||||
"pas": "Комплект для сближения с планетой",
|
"pas": "Комплект для сближения с планетой",
|
||||||
"pc": "Контроллер магнитного снаряда для геологоразведки",
|
"pc": "Контроллер магнитного снаряда для геологоразведки",
|
||||||
"pce": "Каюта пассажира эконом-класса",
|
"pce": "Каюта пассажира эконом-класса",
|
||||||
|
"passenger capacity": "Количество пассажиров",
|
||||||
"pci": "Каюта пассажира бизнес-класса",
|
"pci": "Каюта пассажира бизнес-класса",
|
||||||
"pcm": "Каюта пассажира первого класса",
|
"pcm": "Каюта пассажира первого класса",
|
||||||
"pcq": "Каюта пассажира класса люкс",
|
"pcq": "Каюта пассажира класса люкс",
|
||||||
"pd": "Распределитель питания",
|
"pd": "Распределитель питания",
|
||||||
"pl": "Ипмульсный лазер",
|
"pl": "Импульсный лазер",
|
||||||
"po": "Точечная оборона",
|
"po": "Точечная оборона",
|
||||||
"pp": "Силовая установка",
|
"pp": "Силовая установка",
|
||||||
|
"gpp": "Силовая установка Стражей",
|
||||||
|
"gpd": "Гибридный распределитель питания Стражей",
|
||||||
|
"gpc": "Плазменная пушка Стражей",
|
||||||
|
"ggc": "Пушка Гаусса Стражей",
|
||||||
|
"gsrp": "Набор для усиления щита Стражей",
|
||||||
|
"gfsb": "Ускоритель FSD Стражей",
|
||||||
|
"ghrp": "Набор для усиления корпуса Стражей",
|
||||||
|
"gmrp": "Набор для усиления модуля Стражей",
|
||||||
|
"pwa": "Анализатор импульсных волн",
|
||||||
|
"abl": "Абразивный бластер",
|
||||||
|
"scl": "Пусковая установка для сейсмических снарядов",
|
||||||
|
"sdm": "Вытесняющая ракета для добычи глубинных залежей",
|
||||||
|
"tbsc": "Шоковое орудие",
|
||||||
|
"gsc": "Осколочное орудие Стражей",
|
||||||
"psg": "Призматический щитогенератор",
|
"psg": "Призматический щитогенератор",
|
||||||
"pv": "Гараж для планетарного транспорта",
|
"pv": "Гараж для планетарного транспорта",
|
||||||
"rf": "Устройство переработки",
|
"rf": "Устройство переработки",
|
||||||
|
"rfl": "Зенитная установка (снаряды с дистанционным подрывом)",
|
||||||
"rg": "Электромагнитная пушка",
|
"rg": "Электромагнитная пушка",
|
||||||
|
"rsl": "Дроны-исследователи",
|
||||||
"s": "Сенсоры",
|
"s": "Сенсоры",
|
||||||
"sb": "Усилитель щита",
|
"sb": "Усилитель щита",
|
||||||
"sc": "Сканер обнаружения",
|
"sc": "Сканер обнаружения",
|
||||||
"scb": "Щитонакопитель",
|
"scb": "Щитонакопитель",
|
||||||
|
"sfn": "Нейтрализатор глушащего поля",
|
||||||
"sg": "Щитогенератор",
|
"sg": "Щитогенератор",
|
||||||
"ss": "Сканер Поверхностей",
|
"ss": "Сканер поверхностей",
|
||||||
|
"sua": "Помощь в гиперкрейсерском режиме",
|
||||||
"t": "Маневровые двигатели",
|
"t": "Маневровые двигатели",
|
||||||
"tp": "Торпедная стойка",
|
"tp": "Торпедная стойка",
|
||||||
"ul": "Пульсирующие лазеры",
|
"ul": "Пульсирующие лазеры",
|
||||||
|
"Send To EDEngineer": "Отправить в EDEngineer",
|
||||||
"ws": "Сканер следа FSD",
|
"ws": "Сканер следа FSD",
|
||||||
|
"rpl": "Дроны-ремонтники",
|
||||||
|
"rcpl": "Дроны-разведчики",
|
||||||
|
"xs": "Сканер «инопланетянин»",
|
||||||
|
"tbem": "Блок энзимных ракет",
|
||||||
|
"tbrfl": "Установка для стрельбы стреловидными снарядами с дистанционным подрывом",
|
||||||
|
"dtl": "Дроны-очистители",
|
||||||
|
"mahr": "Набор для усиления корпуса из Метасплава",
|
||||||
"emptyrestricted": "пусто (ограниченно)",
|
"emptyrestricted": "пусто (ограниченно)",
|
||||||
"damage dealt to": "Урон нанесён",
|
"damage dealt to": "Урон нанесен",
|
||||||
"damage received from": "Урон получен от",
|
"damage received from": "Урон получен от",
|
||||||
"against shields": "Против шитов",
|
"against shields": "Против щитов",
|
||||||
"against hull": "Против корпуса",
|
"against hull": "Против корпуса",
|
||||||
"total effective shield": "Общие эффективные щиты",
|
"total effective shield": "Общие эффективные щиты",
|
||||||
"ammunition": "Припасы",
|
"ammunition": "Припасы",
|
||||||
"secs": "с",
|
"secs": "с",
|
||||||
"rebuildsperbay": "Построек за полосу",
|
"bays": "Ячейки",
|
||||||
|
"rebuildsperbay": "Истребителей в ячейке",
|
||||||
|
"mroll": "Roll",
|
||||||
|
"feature": "Свойство",
|
||||||
"worst": "Худшее",
|
"worst": "Худшее",
|
||||||
"average": "Среднее",
|
"average": "Среднее",
|
||||||
"random": "Случайное",
|
"random": "Случайное",
|
||||||
"best": "Лучшее",
|
"best": "Лучшее",
|
||||||
|
"current": "Текущее",
|
||||||
"extreme": "Экстремальное",
|
"extreme": "Экстремальное",
|
||||||
"reset": "Сброс",
|
"reset": "Сброс",
|
||||||
"dpe": "Урон на МДж энергии",
|
"dpe": "Урон на МДж энергии",
|
||||||
@@ -148,8 +192,10 @@
|
|||||||
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
|
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
|
||||||
"eps": "Энергия в секунду",
|
"eps": "Энергия в секунду",
|
||||||
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
||||||
|
"fallofffromrange": "Спад",
|
||||||
|
"falloff": "Спад",
|
||||||
"hps": "Нагрев в секунду",
|
"hps": "Нагрев в секунду",
|
||||||
"hpsshps": "Heat per second (sustained heat per second)",
|
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
|
||||||
"damage by": "Урон",
|
"damage by": "Урон",
|
||||||
"damage from": "Урон от",
|
"damage from": "Урон от",
|
||||||
"shield cells": "Щитонакопители",
|
"shield cells": "Щитонакопители",
|
||||||
@@ -164,13 +210,15 @@
|
|||||||
"internal protection": "Внутренняя защита",
|
"internal protection": "Внутренняя защита",
|
||||||
"external protection": "Внешняя защита",
|
"external protection": "Внешняя защита",
|
||||||
"engagement range": "Боевое расстояние",
|
"engagement range": "Боевое расстояние",
|
||||||
|
"boost interval": "Интервал повыш.",
|
||||||
"total": "Всего",
|
"total": "Всего",
|
||||||
"ammo": "Боекомплект",
|
"ammo": "Макс. боекомплект",
|
||||||
"boot": "Время загрузки",
|
"boot": "Время загрузки",
|
||||||
|
"hacktime": "Время взлома",
|
||||||
"brokenregen": "Скорость восстановления при пробое",
|
"brokenregen": "Скорость восстановления при пробое",
|
||||||
"burst": "Длина очереди",
|
"burst": "Длина очереди",
|
||||||
"burstrof": "Скорострельность очереди",
|
"burstrof": "Скорострельность очереди",
|
||||||
"clip": "Боекомплект",
|
"clip": "Размер боекомплекта",
|
||||||
"damage": "Урон",
|
"damage": "Урон",
|
||||||
"distdraw": "Тяга распределителя",
|
"distdraw": "Тяга распределителя",
|
||||||
"duration": "Продолжительность",
|
"duration": "Продолжительность",
|
||||||
@@ -183,7 +231,7 @@
|
|||||||
"hullreinforcement": "Укрепление корпуса",
|
"hullreinforcement": "Укрепление корпуса",
|
||||||
"integrity": "Целостность",
|
"integrity": "Целостность",
|
||||||
"jitter": "Дрожание",
|
"jitter": "Дрожание",
|
||||||
"kinres": "Сопротивление китетическому урону",
|
"kinres": "Сопротивление кинетическому урону",
|
||||||
"maxfuel": "Макс. топлива на прыжок",
|
"maxfuel": "Макс. топлива на прыжок",
|
||||||
"mass": "Масса",
|
"mass": "Масса",
|
||||||
"optmass": "Оптимизированная масса",
|
"optmass": "Оптимизированная масса",
|
||||||
@@ -199,11 +247,16 @@
|
|||||||
"rof": "Скорострельность",
|
"rof": "Скорострельность",
|
||||||
"angle": "Угол сканера",
|
"angle": "Угол сканера",
|
||||||
"scanrate": "Скорость сканера",
|
"scanrate": "Скорость сканера",
|
||||||
|
"proberadius": "Радиус зонда",
|
||||||
"scantime": "Время сканирования",
|
"scantime": "Время сканирования",
|
||||||
|
"scan range": "Дальность",
|
||||||
|
"max angle": "Макс. угол",
|
||||||
"shield": "Щит",
|
"shield": "Щит",
|
||||||
|
"armour": "Броня",
|
||||||
"shieldboost": "Усиление щитов",
|
"shieldboost": "Усиление щитов",
|
||||||
"shieldreinforcement": "Усилитель щита",
|
"shieldreinforcement": "Усилитель щита",
|
||||||
"shotspeed": "Скорость выстрела",
|
"shotspeed": "Скорость выстрела",
|
||||||
|
"shotdmg": "Урон за выстрел(DPS)",
|
||||||
"spinup": "Время раскрутки",
|
"spinup": "Время раскрутки",
|
||||||
"syscap": "Ресурс систем",
|
"syscap": "Ресурс систем",
|
||||||
"sysrate": "Перезарядка систем",
|
"sysrate": "Перезарядка систем",
|
||||||
@@ -234,9 +287,12 @@
|
|||||||
"explosive": "Взрывч.",
|
"explosive": "Взрывч.",
|
||||||
"kinetic": "Механич.",
|
"kinetic": "Механич.",
|
||||||
"thermal": "Тепл.",
|
"thermal": "Тепл.",
|
||||||
|
"caustic": "Каустич.",
|
||||||
"generator": "Генератор",
|
"generator": "Генератор",
|
||||||
"boosters": "Усилители",
|
"boosters": "Усилители",
|
||||||
"cells": "Ячейки",
|
"cells": "Ячейки",
|
||||||
|
"shield addition": "ДОбавления к щиту",
|
||||||
|
"jump addition": "ДОбавления к прыжку",
|
||||||
"bulkheads": "Переборки",
|
"bulkheads": "Переборки",
|
||||||
"reinforcement": "Усилители",
|
"reinforcement": "Усилители",
|
||||||
"power and costs": "Энергия и стоимость",
|
"power and costs": "Энергия и стоимость",
|
||||||
@@ -250,7 +306,7 @@
|
|||||||
"damage to opponent's shields": "Урон щиту противника",
|
"damage to opponent's shields": "Урон щиту противника",
|
||||||
"damage to opponent's hull": "Урон корпусу противника",
|
"damage to opponent's hull": "Урон корпусу противника",
|
||||||
"offence": "Нападение",
|
"offence": "Нападение",
|
||||||
"defence": "Оборона",
|
"defence": "Защита",
|
||||||
"shield metrics": "Данные щита",
|
"shield metrics": "Данные щита",
|
||||||
"raw shield strength": "Чистая мощность щита",
|
"raw shield strength": "Чистая мощность щита",
|
||||||
"shield sources": "Ресурсы щита",
|
"shield sources": "Ресурсы щита",
|
||||||
@@ -265,28 +321,70 @@
|
|||||||
"defence metrics": "Данные обороны",
|
"defence metrics": "Данные обороны",
|
||||||
"fuel carried": "Топливо на борту",
|
"fuel carried": "Топливо на борту",
|
||||||
"cargo carried": "Груз на борту",
|
"cargo carried": "Груз на борту",
|
||||||
"ship control": "Управление кораблём",
|
"ship control": "Управление кораблем",
|
||||||
"opponent": "Противник",
|
"opponent": "Противник",
|
||||||
"opponent's shields": "Щит противника",
|
"opponent's shields": "Щит противника",
|
||||||
"opponent's armour": "Броня противника",
|
"opponent's armour": "Броня противника",
|
||||||
|
"overall damage": "общий урон",
|
||||||
|
"overall": "общий",
|
||||||
"shield damage sources": "источники урона по щиту",
|
"shield damage sources": "источники урона по щиту",
|
||||||
"armour damage sources": "источники урона по броне",
|
"armour damage sources": "источники урона по броне",
|
||||||
"never": "Никогда",
|
"never": "Никогда",
|
||||||
"stock": "базовый",
|
"stock": "базовый",
|
||||||
"boost": "форсаж",
|
"boost": "форсаж",
|
||||||
|
"tab_defence": "защита",
|
||||||
|
"federation rank 1": "Рекрут",
|
||||||
|
"federation rank 2": "Кадет",
|
||||||
|
"federation rank 3": "Гардемарин",
|
||||||
|
"federation rank 4": "Старшина",
|
||||||
|
"federation rank 5": "Главный старшина",
|
||||||
|
"federation rank 6": "Уорент-офицер",
|
||||||
|
"federation rank 7": "Энсин",
|
||||||
|
"federation rank 8": "Лейтенант",
|
||||||
|
"federation rank 9": "Капитан-лейтенант",
|
||||||
|
"federation rank 10": "Начальник гарнизона",
|
||||||
|
"federation rank 11": "Командир корабля",
|
||||||
|
"federation rank 12": "Контр-адмирал",
|
||||||
|
"federation rank 13": "Вице-адмирал",
|
||||||
|
"federation rank 14": "Адмирал",
|
||||||
|
"federation rank required": "Минимальный ранг федерации для покупки",
|
||||||
|
"empire rank 1": "Чужак",
|
||||||
|
"empire rank 2": "Крепостной",
|
||||||
|
"empire rank 3": "Мастер",
|
||||||
|
"empire rank 4": "Оруженосец",
|
||||||
|
"empire rank 5": "Рыцарь",
|
||||||
|
"empire rank 6": "Лорд",
|
||||||
|
"empire rank 7": "Барон",
|
||||||
|
"empire rank 8": "Виконт",
|
||||||
|
"empire rank 9": "Граф",
|
||||||
|
"empire rank 10": "Эрл",
|
||||||
|
"empire rank 11": "Маркиз",
|
||||||
|
"empire rank 12": "Герцог",
|
||||||
|
"empire rank 13": "Принц",
|
||||||
|
"empire rank 14": "Король",
|
||||||
|
"empire rank required": "Минимальный ранг империи для покупки",
|
||||||
|
"kg": "кг",
|
||||||
|
"kg/s": "кг/с",
|
||||||
|
"km": "км",
|
||||||
|
"m": "м",
|
||||||
|
"MJ": "МДж",
|
||||||
|
"MW": "МВт",
|
||||||
|
"T": "т",
|
||||||
|
"°/s": "°/с",
|
||||||
"/s": "/с",
|
"/s": "/с",
|
||||||
|
"/min": "/мин",
|
||||||
"m/s": "м/с",
|
"m/s": "м/с",
|
||||||
"Ls": "Св.сек",
|
"Ls": "Св.сек",
|
||||||
"LY": "Св.лет",
|
"LY": "Св.лет",
|
||||||
"CR": "кр.",
|
"CR": "кр.",
|
||||||
"S": "M",
|
"S": "М",
|
||||||
"M": "С",
|
"M": "С",
|
||||||
"L": "б",
|
"L": "Б",
|
||||||
"H": "O",
|
"H": "О",
|
||||||
"U": "B",
|
"U": "В",
|
||||||
"small": "Малый",
|
"small": "Малый",
|
||||||
"medium": "Средний",
|
"medium": "Средний",
|
||||||
"large": "большой",
|
"large": "Большой",
|
||||||
"alpha": "Альфа",
|
"alpha": "Альфа",
|
||||||
"beta": "Бета",
|
"beta": "Бета",
|
||||||
"standard": "Стандартный",
|
"standard": "Стандартный",
|
||||||
@@ -299,28 +397,29 @@
|
|||||||
"edit data": "Редактирование",
|
"edit data": "Редактирование",
|
||||||
"empty all": "пусто все",
|
"empty all": "пусто все",
|
||||||
"Enter Name": "Введите имя",
|
"Enter Name": "Введите имя",
|
||||||
"fastest range": "быстрый диапазон",
|
"farthest range": "быстрый диапазон",
|
||||||
"fuel level": "уровень топлива",
|
"fuel level": "уровень топлива",
|
||||||
"full tank": "Полный бак",
|
"full tank": "Полный бак",
|
||||||
"internal compartments": "внутренние отсеки",
|
"internal compartments": "внутренние отсеки",
|
||||||
"jump range": "Дальность прыжка",
|
"jump range": "Дальность прыжка",
|
||||||
"mass lock factor": "Масс. блок",
|
"mass lock factor": "Коэффициент гравитационного захвата",
|
||||||
"max mass": "Максимальная масса",
|
"max mass": "Максимальная масса",
|
||||||
|
"minimum mass": "Минимальная масса",
|
||||||
|
"optimal mass": "Оптимальная масса",
|
||||||
"net cost": "разница в цене",
|
"net cost": "разница в цене",
|
||||||
"none created": "не создано",
|
"none created": "не создано",
|
||||||
"refuel time": "Время дозаправки",
|
"refuel time": "Время дозаправки",
|
||||||
"retrofit from": "модификация от",
|
"retrofit from": "модификация от",
|
||||||
"T-Load": "Тепл.",
|
"T-Load": "Тепл.",
|
||||||
"utility mounts": "Вспомогательное оборудование",
|
"utility mounts": "Вспомогательное оборудование",
|
||||||
"about": "О ...",
|
"about": "О сайте",
|
||||||
"action": "Действие",
|
"action": "Действие",
|
||||||
"added": "Добавлено",
|
"added": "Добавлено",
|
||||||
"armour": "Броня",
|
|
||||||
"available": "доступно",
|
"available": "доступно",
|
||||||
"backup": "Резервная копия",
|
"backup": "Резервная копия",
|
||||||
"bins": "контейнеры",
|
"bins": "контейнеры",
|
||||||
"build": "cборка",
|
"build": "сборка",
|
||||||
"builds": "cборки",
|
"builds": "сборки",
|
||||||
"buy": "купить",
|
"buy": "купить",
|
||||||
"cancel": "отменить",
|
"cancel": "отменить",
|
||||||
"cargo": "Груз",
|
"cargo": "Груз",
|
||||||
@@ -339,16 +438,16 @@
|
|||||||
"DPS": "УВС",
|
"DPS": "УВС",
|
||||||
"efficiency": "Эффективность",
|
"efficiency": "Эффективность",
|
||||||
"empty": "пусто",
|
"empty": "пусто",
|
||||||
"ENG": "ДВИ",
|
"ENG": "ДВГ",
|
||||||
"export": "Экспорт",
|
"export": "Экспорт",
|
||||||
"forum": "Форум",
|
"forum": "Форум",
|
||||||
"fuel": "Топливо",
|
"fuel": "Топл.",
|
||||||
"hardpoints": "Орудийные порты",
|
"hardpoints": "Орудийные порты",
|
||||||
"hull": "Корпус",
|
"hull": "Корпус",
|
||||||
"import": "импортировать ",
|
"import": "импортировать ",
|
||||||
"insurance": "Страховка",
|
"insurance": "Страховка",
|
||||||
"jumps": "Прыжков",
|
"jumps": "Прыжков",
|
||||||
"laden": "Груженый",
|
"laden": "Груж",
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"maneuverability": "Маневренность",
|
"maneuverability": "Маневренность",
|
||||||
"max": "Макс",
|
"max": "Макс",
|
||||||
@@ -361,13 +460,15 @@
|
|||||||
"rate": "скорость",
|
"rate": "скорость",
|
||||||
"rename": "Переименовать",
|
"rename": "Переименовать",
|
||||||
"repair": "Починка",
|
"repair": "Починка",
|
||||||
"ret": "Убр.",
|
"ret": "Убр",
|
||||||
"retracted": "Убрано",
|
"retracted": "Убрано",
|
||||||
"ROF": "В/сек",
|
"ROF": "В\/сек",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"sell": "Продать",
|
"sell": "Продать",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"shields": "Щиты",
|
"shields": "Щиты",
|
||||||
|
"No Shield": "Нет щита",
|
||||||
|
"Never": "Никогда",
|
||||||
"ship": "Корабль",
|
"ship": "Корабль",
|
||||||
"ships": "Корабли",
|
"ships": "Корабли",
|
||||||
"shortened": "Укороченный",
|
"shortened": "Укороченный",
|
||||||
@@ -377,8 +478,296 @@
|
|||||||
"SYS": "СИС",
|
"SYS": "СИС",
|
||||||
"time": "Время",
|
"time": "Время",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
"unladen": "Пустой",
|
"unladen": "Пуст",
|
||||||
"URL": "Ссылка",
|
"URL": "Ссылка",
|
||||||
"WEP": "ОРУЖ",
|
"WEP": "ОРУ",
|
||||||
"yes": "Да"
|
"yes": "Да",
|
||||||
|
"crew": "экипаж",
|
||||||
|
"jump": "прыж",
|
||||||
|
"pax": "псж",
|
||||||
|
"RST": "СБР",
|
||||||
|
"grade": "уровень",
|
||||||
|
"total laden": "всего груж",
|
||||||
|
"total unladen": "всего пуст",
|
||||||
|
"module": "модуль",
|
||||||
|
"announcements": "объявления",
|
||||||
|
"resistance": "сопротивление",
|
||||||
|
"Lightweight Alloy": "Легкие сплавы",
|
||||||
|
"base": "базовые",
|
||||||
|
"core module classes": "основные модули",
|
||||||
|
"Group highlighted ships": "Сгруппировать выделенные корабли",
|
||||||
|
"tooltips": "всплывающие подсказки",
|
||||||
|
"module resistances": "сопротивление модулей",
|
||||||
|
"power plant": "силовая установка",
|
||||||
|
"thrusters": "маневровые двигатели",
|
||||||
|
"frame shift drive": "рамочно-сместительный двигатель",
|
||||||
|
"life support": "система жизнеобеспечения",
|
||||||
|
"power distributor": "распределитель питания",
|
||||||
|
"sensors": "сенсоры",
|
||||||
|
"fuel tank": "топливный бак",
|
||||||
|
"resting heat (Beta)": "тепло покоя (бета)",
|
||||||
|
"hull hardness": "Прочность корпуса",
|
||||||
|
"weapon": "оружие",
|
||||||
|
"maximum speed": "максимальная скорость",
|
||||||
|
"maximum range": "максимальная дальность",
|
||||||
|
"shortlink": "короткая ссылка",
|
||||||
|
"guardian": "стражи",
|
||||||
|
"engineers": "инженеры",
|
||||||
|
"component": "компонент",
|
||||||
|
"amount": "кол-во",
|
||||||
|
"core internal": "основное оборуднование",
|
||||||
|
"optional internal": "доп. оборудование",
|
||||||
|
"Heat Sink Launcher": "Теплоотводная катапульта",
|
||||||
|
"scanners": "сканеры",
|
||||||
|
"experimental": "экспериментальное",
|
||||||
|
"mining": "шахтерство",
|
||||||
|
"lasers": "лазеры",
|
||||||
|
"ordnance": "артиллерия",
|
||||||
|
"projectiles": "с боеприпасами",
|
||||||
|
"hangars": "ангары",
|
||||||
|
"limpet controllers": "контроллеры снарядов",
|
||||||
|
"passenger cabins": "каюты пассажиров",
|
||||||
|
"structural reinforcement": "структурные усиления",
|
||||||
|
"flight assists": "помощники в полете",
|
||||||
|
"modifications": "модификации",
|
||||||
|
"wep_reload": "перезарядка",
|
||||||
|
"optimal multiplier": "оптимальный усилитель",
|
||||||
|
"Cargo Hatch": "Грузовой люк",
|
||||||
|
"Chaff Launcher": "Разбрасыватель дипольных отражателей",
|
||||||
|
"Point Defence": "Точечная оборона",
|
||||||
|
"Electronic Countermeasure": "Электронное противодействие",
|
||||||
|
"Xeno Scanner": "Сканер «инопланетянин»",
|
||||||
|
"Shutdown Field Neutraliser": "Нейтрализатор глушащего поля",
|
||||||
|
"Disruptor": "Диверсант",
|
||||||
|
"Pacifier": "Миротворец",
|
||||||
|
"Advanced Plasma Accelerator": "Улучшенный ускоритель плазмы",
|
||||||
|
"Cytoscrambler": "Дезинтегратор",
|
||||||
|
"Retributor": "Каратель",
|
||||||
|
"Enforcer": "Убийца",
|
||||||
|
"Imperial Hammer": "Имперский молот",
|
||||||
|
"Rocket Propelled FSD Disruptor": "Ракетный FSD-разрушитель",
|
||||||
|
"Pack-Hound": "Гончие",
|
||||||
|
"Shock Mine Launcher": "Установщик шоковых мин",
|
||||||
|
"Mining Lance": "Копье шахтера",
|
||||||
|
"Corrosion Resistant": "Коррозийно-устойчивый стеллаж",
|
||||||
|
"Standard Docking Computer": "Стандартный стыковочный компьютер",
|
||||||
|
"Advanced Docking Computer": "Улучшенный стыковочный компьютер",
|
||||||
|
"Detailed Surface Scanner": "Подробный сканер поверхности",
|
||||||
|
"Supercruise Assist": "Помощь в гиперкрейсерском режиме",
|
||||||
|
"Guardian Power Distributor": "Рапределитель питания Стражей",
|
||||||
|
"Enhanced Performance": "Усиленные маневровые двигатели",
|
||||||
|
"Guardian Hybrid Power Plant": "Гибридная силовая установка Стражей",
|
||||||
|
"Reinforced Alloy": "Укрепленные сплавы",
|
||||||
|
"Military Grade Composite": "Композит военного класса",
|
||||||
|
"Mirrored Surface Composite": "Композит с зеркальной поверхностью",
|
||||||
|
"Reactive Surface Composite": "Композит с реактивной поверхностью",
|
||||||
|
"Proto Light Alloys": "Опытные легкие сплавы",
|
||||||
|
"Ammo capacity": "Вместимость магазина",
|
||||||
|
"Lightweight": "Облегченный",
|
||||||
|
"Reinforced": "Усиленный",
|
||||||
|
"Shielded": "Защищенный",
|
||||||
|
"Fast scan": "Быстрое сканирование",
|
||||||
|
"Long range": "Дальнего действия",
|
||||||
|
"Wide angle": "Широкоугольный",
|
||||||
|
"Blast resistant": "Взрывостойкий",
|
||||||
|
"Heavy duty": "Надежный",
|
||||||
|
"Kinetic resistant": "Противокинетический",
|
||||||
|
"Resistance augmented": "С универсальной защитой",
|
||||||
|
"Thermal resistant": "Термостойкий",
|
||||||
|
"Thermo Block": "Блокировка тепла",
|
||||||
|
"Force Block": "Усиленная блокировка",
|
||||||
|
"Blast Block": "Блокировка взрыва",
|
||||||
|
"Flow Control": "Контроль интенсивности",
|
||||||
|
"Double Braced": "Двойная прочность",
|
||||||
|
"Super Capacitors": "Суперконденсаторы",
|
||||||
|
"Efficient": "Эффективный",
|
||||||
|
"Focused": "Точный",
|
||||||
|
"Overcharged": "Усиленный",
|
||||||
|
"Rapid fire": "Скорострельный",
|
||||||
|
"Short range": "Ближнего действия",
|
||||||
|
"Sturdy": "Прочный",
|
||||||
|
"Oversized": "Сверхразмер",
|
||||||
|
"Stripped Down": "Урезанный вариант",
|
||||||
|
"Phasing sequence": "Последовательность фазирования",
|
||||||
|
"Concordant sequence": "Последовательность координации",
|
||||||
|
"Scramble spectrum": "Отключающая сетка",
|
||||||
|
"Thermal shock": "Тепловой удар",
|
||||||
|
"Emissive munitions": "Эмиссионные припасы",
|
||||||
|
"Multi-servos": "Сервосистема",
|
||||||
|
"Inertial impact": "Инерционный удар",
|
||||||
|
"Thermal vent": "Теплоотдача",
|
||||||
|
"Regeneration sequence": "Последовательность восстановления",
|
||||||
|
"Thermal conduit": "Проводник тепла",
|
||||||
|
"High capacity": "Вместительный магазин",
|
||||||
|
"Incendiary rounds": "Зажигательные припасы",
|
||||||
|
"Auto loader": "Автоматическая система заряжения",
|
||||||
|
"Smart rounds": "Умные боеприпасы",
|
||||||
|
"Corrosive shell": "Разъедающие припасы",
|
||||||
|
"Force shell": "Усиленные снаряды",
|
||||||
|
"High yield shell": "Снаряд большой мощности",
|
||||||
|
"Dispersal field": "Рассеивающее поле",
|
||||||
|
"Thermal cascade": "Термический залп",
|
||||||
|
"Double shot": "Двойной выстрел",
|
||||||
|
"Dazzle shell": "Ослепляющие снаряды",
|
||||||
|
"Screening shell": "Заслоняющие снаряды",
|
||||||
|
"Drag munitions": "Замедляющие боеприпасы",
|
||||||
|
"Target lock breaker": "Генератор помех для системы захвата цели",
|
||||||
|
"Plasma Slug": "Плазменный рельсовый снаряд",
|
||||||
|
"Feedback Cascade": "Ответный запл",
|
||||||
|
"Super Penetrator": "Модуль сверхпробития",
|
||||||
|
"Overload munitions": "Вызывающие перезагрузку боеприпасы",
|
||||||
|
"Penetrator payload": "Бронебойные снаряды",
|
||||||
|
"Penetrator Payload": "Бронебойные снаряды",
|
||||||
|
"FSD interrupt": "Помеха для FSD",
|
||||||
|
"Penetrator Munitions": "Бронебойные боеголовки",
|
||||||
|
"Mass lock munition": "Боеприпасы с гравитационным захватом",
|
||||||
|
"Mass Lock Munition": "Боеприпасы с гравитационным захватом",
|
||||||
|
"Reverberating cascade": "Отраженный залп",
|
||||||
|
"Shift-lock canister": "Рамоблокирующая кассета",
|
||||||
|
"Ion disruptor": "Ионный дестабилизатор",
|
||||||
|
"Radiant Canister": "Светящаяся кассета",
|
||||||
|
"Expanded capture arc": "Расширенная дуга захвата",
|
||||||
|
"Enhanced low power": "Улучшенное энергосбережение",
|
||||||
|
"Fast Charge": "Быстрый заряд",
|
||||||
|
"Multi-weave": "Мультипрошивка",
|
||||||
|
"Hi-Cap": "Высокая ёмкость",
|
||||||
|
"Lo-draw": "Пониженное потребление",
|
||||||
|
"Rapid charge": "Быстрая зарядка",
|
||||||
|
"Specialised": "Адаптивный",
|
||||||
|
"Boss Cells": "Босс-ячейки",
|
||||||
|
"Recycling Cell": "Рециркуляционная ячейка",
|
||||||
|
"Reflective Plating": "Отражающая броня",
|
||||||
|
"Angled Plating": "Угловая броня",
|
||||||
|
"Layered Plating": "Многослойная броня",
|
||||||
|
"Deep Plating": "Утолщенная броня",
|
||||||
|
"Expanded Probe Scanning Radius": "Увеличение радиуса сканирования зондов",
|
||||||
|
"High charge capacity": "Высокоёмкий",
|
||||||
|
"Charge enhanced": "Быстрозаряжающийся",
|
||||||
|
"Engine focused": "Фокус на двигатель",
|
||||||
|
"System focused": "Фокус на систему",
|
||||||
|
"Weapon focused": "Фокус на орудия",
|
||||||
|
"Super Conduits": "Сверхпроводники",
|
||||||
|
"Cluster Capacitors": "Кассетные конденсаторы",
|
||||||
|
"Increased range": "Увеличенная дальность",
|
||||||
|
"Faster boot sequence": "Ускорение запуска",
|
||||||
|
"Deep Charge": "Заряд повышенной мощности",
|
||||||
|
"Thermal Spread": "Рассеивание тепла",
|
||||||
|
"Mass Manager": "Распределитель гравитации",
|
||||||
|
"Dirty": "«Грязная» донастройка",
|
||||||
|
"Clean": "«Чистая» донастройка",
|
||||||
|
"Drag Drives": "Ускорители",
|
||||||
|
"Drive Distributors": "Распределители тяги",
|
||||||
|
"Armoured": "Бронированный",
|
||||||
|
"Low emissions": "Малое излучение",
|
||||||
|
"Monstered": "Монстрация",
|
||||||
|
"roles": "роли",
|
||||||
|
"Maximize Jump Range": "Максимизировать дальность прыжка",
|
||||||
|
"Multi-purpose": "Многоцелевой",
|
||||||
|
"Combat": "Боец",
|
||||||
|
"Trader": "Торговец",
|
||||||
|
"Explorer": "Исследователь",
|
||||||
|
"Planetary Explorer": "Планетарный исследователь",
|
||||||
|
"Miner": "Шахтер",
|
||||||
|
"Racer": "Гонщик",
|
||||||
|
|
||||||
|
"Aberrant Shield Pattern Analysis": "Анализ аномального поведения щита",
|
||||||
|
"Abnormal Compact Emissions Data": "Аномальные компактные данные об излучении",
|
||||||
|
"Aerogel": "Аэрогель",
|
||||||
|
"Adaptive Encryptors Capture": "Захват адаптивного шифровальщика",
|
||||||
|
"Anomalous Bulk Scan Data": "Аномальный массив данных сканирования",
|
||||||
|
"Anomalous FSD Telemetry": "Аномальная телеметрия FSD",
|
||||||
|
"Antimony": "Сурьма",
|
||||||
|
"Arsenic": "Мышьяк",
|
||||||
|
"Atypical Disrupted Wake Echoes": "Атипичное эхо поврежденного следа",
|
||||||
|
"Atypical Encryption Archives": "Нетипичные архивы шифрования",
|
||||||
|
"Basic Conductors": "Простые проводники",
|
||||||
|
"Bio-Mechanical Conduits": "Биомеханические энергопроводники",
|
||||||
|
"Biotech Conductors": "Биотехнические проводники",
|
||||||
|
"Boron": "Бор",
|
||||||
|
"Cadmium": "Кадмий",
|
||||||
|
"Carbon": "Углерод",
|
||||||
|
"Carbon Fibre Plating": "Углеволоконная броня",
|
||||||
|
"Chemical Distillery": "Оборудование для перегонки химикатов",
|
||||||
|
"Chemical Manipulators": "Манипуляторы для работы с химикатами",
|
||||||
|
"Chemical Processors": "Оборудование для химобработки",
|
||||||
|
"Chromium": "Хром",
|
||||||
|
"Classified Scan Databanks": "Засекреченные базы данных сканированоя",
|
||||||
|
"Classified Scan Fragment": "Засекреченные фрагменты данных сканирования",
|
||||||
|
"Compound Shielding": "Многоступенчатая защита",
|
||||||
|
"Conductive Ceramics": "Проводящая керамика",
|
||||||
|
"Conductive Components": "Проводящие компоненты",
|
||||||
|
"Conductive Polymers": "Проводящие полимеры",
|
||||||
|
"Configurable Components": "Настраиваемые компоненты",
|
||||||
|
"Core Dynamics Composites": "Композиты Core Dynamics",
|
||||||
|
"Cracked Industrial Firmware": "Взломанные промышленные микропрограммы",
|
||||||
|
"Datamined Wake Exceptions": "Исключения из глубинного анализа данных следа",
|
||||||
|
"Decoded Emission Data": "Расшифрованные данные об излучении",
|
||||||
|
"Distorted Shield Cycle Recordings": "Поврежденные цикличные записи щита",
|
||||||
|
"Divergent Scan Data": "Неформатные данные сканирования",
|
||||||
|
"Eccentric Hyperspace Trajectories": "Аномальные траектории в гиперпространстве",
|
||||||
|
"Electrochemical Arrays": "Электрохимические массивы",
|
||||||
|
"Exceptional Scrambled Emission Data": "Исключительные зашифрованные данные об излучении",
|
||||||
|
"Exquisite Focus Crystals": "Отборные фокусировочные кристаллы",
|
||||||
|
"Flawed Focus Crystals": "Поврежденные фокусировочные кристаллы",
|
||||||
|
"Focus Crystals": "Фокусировочные кристаллы",
|
||||||
|
"Galvanising Alloys": "Сплавы для гальванизации",
|
||||||
|
"Germanium": "Германий",
|
||||||
|
"Grid Resistors": "Наборные резисторы",
|
||||||
|
"Heat Conduction Wiring": "Теплопроводящие провода",
|
||||||
|
"Heat Dispersion Plate": "Теплорассеивающая пластина",
|
||||||
|
"Heat Exchangers": "Теплообменные агрегаты",
|
||||||
|
"Heat Vanes": "Тепловые заслонки",
|
||||||
|
"High Density Composites": "Высокоплотные композиты",
|
||||||
|
"Hybrid Capacitors": "Гибридные конденсаторы",
|
||||||
|
"Imperial Shielding": "Имперская защита",
|
||||||
|
"Improvised Components": "Кустарные компоненты",
|
||||||
|
"Inconsistent Shield Soak Analysis": "Неполный анализ поглощения щита",
|
||||||
|
"Iron": "Железо",
|
||||||
|
"Irregular Emission Data": "Нестандартные данные об излучении",
|
||||||
|
"Manganese": "Марганец",
|
||||||
|
"Mechanical Components": "Механические компоненты",
|
||||||
|
"Mechanical Equipment": "Механическое оборудование",
|
||||||
|
"Mechanical Scrap": "Механические отходы",
|
||||||
|
"Mercury": "Ртуть",
|
||||||
|
"Military Grade Alloys": "Сплавы военного назначения",
|
||||||
|
"Military Supercapacitors": "Военные суперконденсаторы",
|
||||||
|
"Modified Consumer Firmware": "Измененные пользовательские микропрограммы",
|
||||||
|
"Modified Embedded Firmware": "Измененные встроенные микропрограммы",
|
||||||
|
"Molybdenum": "Молибден",
|
||||||
|
"Nickel": "Никель",
|
||||||
|
"Niobium": "Ниобий",
|
||||||
|
"Open Symmetric Keys": "Открытые симметричные ключи",
|
||||||
|
"Pharmaceutical Isolators": "Фармацевтические изоляционные материалы",
|
||||||
|
"Phase Alloys": "Фазовые сплавы",
|
||||||
|
"Phosphorus": "Фосфор",
|
||||||
|
"Polymer Capacitors": "Полимерные конденсаторы",
|
||||||
|
"Precipitated Alloys": "Осажденные сплавы",
|
||||||
|
"Proprietary Composites": "Патентованные композиты",
|
||||||
|
"Proto Heat Radiators": "Прототипы теплоизлучателей",
|
||||||
|
"Proto Radiolic Alloys": "Сплавы для изготовления зондов",
|
||||||
|
"Refined Focus Crystals": "Обработанные фокусировочные кристаллы",
|
||||||
|
"Ruthenium": "Рутений",
|
||||||
|
"Salvaged Alloys": "Захваченные сплавы",
|
||||||
|
"Security Firmware Patch": "Обновление для защитной микропрограммы",
|
||||||
|
"Selenium": "Селениум",
|
||||||
|
"Shield Emitters": "Щитоизлучатели",
|
||||||
|
"Shielding Sensors": "Сенсоры системы экранирования",
|
||||||
|
"Specialised Legacy Firmware": "Специальные микропрограммы предыдущего поколения",
|
||||||
|
"Strange Wake Solutions": "Странные расчеты следа",
|
||||||
|
"Sulphur": "Сера",
|
||||||
|
"Tagged Encryption Codes": "Меченные шифровальные коды",
|
||||||
|
"Technetium": "Технеций",
|
||||||
|
"Tellurium": "Теллурий",
|
||||||
|
"Thermic Alloys": "Термические сплавы",
|
||||||
|
"Tin": "Олово",
|
||||||
|
"Tungsten": "Вольфрам",
|
||||||
|
"Unexpected Emission Data": "Неожиданные данные об излучении",
|
||||||
|
"Unidentified Scan Archives": "Неопознанные архивы сканирования",
|
||||||
|
"Untypical Shield Scans": "Нетипичные данные сканирования щитов",
|
||||||
|
"Unusual Encrypted Files": "Особые зашифрованные файлы",
|
||||||
|
"Vanadium": "Ванадий",
|
||||||
|
"Worn Shield Emitters": "Изношенные щитоизлучатели",
|
||||||
|
"Yttrium": "Иттрий",
|
||||||
|
"Zinc": "Цинк",
|
||||||
|
"Zirconium": "Цирконий"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'babel-polyfill';
|
import '@babel/polyfill';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import '../less/app.less';
|
import '../less/app.less';
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { CoriolisLogo, GitHub } from '../components/SvgIcons';
|
|||||||
* About Page
|
* About Page
|
||||||
*/
|
*/
|
||||||
export default class AboutPage extends Page {
|
export default class AboutPage extends Page {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -23,27 +22,79 @@ export default class AboutPage extends Page {
|
|||||||
* @return {React.Component} The page contents
|
* @return {React.Component} The page contents
|
||||||
*/
|
*/
|
||||||
renderPage() {
|
renderPage() {
|
||||||
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
|
return (
|
||||||
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
|
<div
|
||||||
|
className={'page'}
|
||||||
|
style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}
|
||||||
|
>
|
||||||
|
<h1>
|
||||||
|
<CoriolisLogo style={{ marginRight: '0.4em' }} className="xl" />
|
||||||
|
<span className="warning">Coriolis EDCD Edition</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
|
<p>
|
||||||
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
|
This is a clone of the Coriolis project, whose original author is
|
||||||
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
|
currently unable to maintain it. This clone is maintained by the{' '}
|
||||||
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
|
<a href="http://edcd.github.io/">EDCD community</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To recover your builds, go to{' '}
|
||||||
|
<a href="https://coriolis.io/" target="_blank">
|
||||||
|
https://coriolis.io/
|
||||||
|
</a>
|
||||||
|
, backup your builds (Settings / Backup), copy the text, return here
|
||||||
|
and import (Settings / Import).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The Coriolis project was inspired by{' '}
|
||||||
|
<a href="http://www.edshipyard.com/" target="_blank">
|
||||||
|
E:D Shipyard
|
||||||
|
</a>{' '}
|
||||||
|
and, of course,{' '}
|
||||||
|
<a href="http://www.elitedangerous.com" target="_blank">
|
||||||
|
Elite Dangerous
|
||||||
|
</a>
|
||||||
|
. The ultimate goal of Coriolis is to provide rich features to support
|
||||||
|
in-game play and planning while engaging the E:D community to support
|
||||||
|
its development.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Coriolis was created using assets and imagery from Elite: Dangerous,
|
||||||
|
with the permission of Frontier Developments plc, for non-commercial
|
||||||
|
purposes. It is not endorsed by nor reflects the views or opinions of
|
||||||
|
Frontier Developments. A number of assets were sourced from{' '}
|
||||||
|
<a href="http://edassets.org" target="_blank">
|
||||||
|
ED Assets
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
|
<a
|
||||||
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
|
style={{ display: 'block', textDecoration: 'none' }}
|
||||||
|
href="https://github.com/EDCD/coriolis"
|
||||||
|
target="_blank"
|
||||||
|
title="Coriolis Github Project"
|
||||||
|
>
|
||||||
|
<GitHub style={{ margin: '0.4em' }} className="l fg xl" />
|
||||||
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
|
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
|
||||||
github.com/EDCD/coriolis
|
github.com/EDCD/coriolis
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
|
<p>
|
||||||
|
Coriolis is an open source project. Checkout the list of upcoming
|
||||||
|
features and to-do list on github. Any and all contributions and
|
||||||
|
feedback are welcome. If you encounter any bugs please report them and
|
||||||
|
provide as much detail as possible.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3>Chat</h3>
|
<h3>Chat</h3>
|
||||||
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
|
<p>
|
||||||
|
You can chat to us on our{' '}
|
||||||
<h3>Supporting Coriolis</h3>
|
<a href="https://discord.gg/0uwCh6R62aPRjk9w" target="_blank">
|
||||||
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
|
EDCD Discord server
|
||||||
</div>;
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,523 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import Page from './Page';
|
|
||||||
import Router from '../Router';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Ship from '../shipyard/Ship';
|
|
||||||
import { fromComparison, toComparison } from '../shipyard/Serializer';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { SizeMap, ShipFacets } from '../shipyard/Constants';
|
|
||||||
import ComparisonTable from '../components/ComparisonTable';
|
|
||||||
import BarChart from '../components/BarChart';
|
|
||||||
import ModalCompare from '../components/ModalCompare';
|
|
||||||
import ModalExport from '../components/ModalExport';
|
|
||||||
import ModalPermalink from '../components/ModalPermalink';
|
|
||||||
import ModalImport from '../components/ModalImport';
|
|
||||||
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
|
|
||||||
import ShortenUrl from '../utils/ShortenUrl';
|
|
||||||
import { comparisonBBCode } from '../utils/BBCode';
|
|
||||||
const browser = require('detect-browser');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a comparator based on the specified predicate
|
|
||||||
* @param {string} predicate Predicate / propterty name
|
|
||||||
* @return {Function} Comparator
|
|
||||||
*/
|
|
||||||
function sortBy(predicate) {
|
|
||||||
return (a, b) => {
|
|
||||||
if (a[predicate] === b[predicate]) {
|
|
||||||
if (a.name == b.name) {
|
|
||||||
a.buildName.toLowerCase() > b.buildName.toLowerCase() ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
|
|
||||||
}
|
|
||||||
if (typeof a[predicate] == 'string') {
|
|
||||||
return a[predicate].toLowerCase() > b[predicate].toLowerCase() ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a[predicate] > b[predicate] ? 1 : -1;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comparison Page
|
|
||||||
*/
|
|
||||||
export default class ComparisonPage extends Page {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this._sortShips = this._sortShips.bind(this);
|
|
||||||
this._buildsSelected = this._buildsSelected.bind(this);
|
|
||||||
this._updateDiscounts = this._updateDiscounts.bind(this);
|
|
||||||
this.state = this._initState(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Re]Create initial state from context
|
|
||||||
* @param {context} context React component context
|
|
||||||
* @return {Object} New state object
|
|
||||||
*/
|
|
||||||
_initState(context) {
|
|
||||||
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
|
|
||||||
let params = context.route.params;
|
|
||||||
let code = params.code;
|
|
||||||
let name = params.name ? decodeURIComponent(params.name) : null;
|
|
||||||
let newName = '';
|
|
||||||
let compareMode = !code;
|
|
||||||
let builds = [];
|
|
||||||
let saved = false;
|
|
||||||
let predicate = 'name';
|
|
||||||
let desc = false;
|
|
||||||
let importObj = {};
|
|
||||||
|
|
||||||
if (compareMode) {
|
|
||||||
if (name == 'all') {
|
|
||||||
let allBuilds = Persist.getBuilds();
|
|
||||||
newName = name;
|
|
||||||
for (let shipId in allBuilds) {
|
|
||||||
for (let buildName in allBuilds[shipId]) {
|
|
||||||
if (buildName && allBuilds[shipId][buildName]) {
|
|
||||||
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let comparisonData = Persist.getComparison(name);
|
|
||||||
if (comparisonData) {
|
|
||||||
defaultFacets = comparisonData.facets;
|
|
||||||
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
|
|
||||||
saved = true;
|
|
||||||
newName = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let comparisonData = toComparison(code);
|
|
||||||
defaultFacets = comparisonData.f;
|
|
||||||
newName = name = comparisonData.n;
|
|
||||||
predicate = comparisonData.p;
|
|
||||||
desc = comparisonData.d;
|
|
||||||
comparisonData.b.forEach((build) => {
|
|
||||||
builds.push(this._createBuild(build.s, build.n, build.c));
|
|
||||||
if (!importObj[build.s]) {
|
|
||||||
importObj[build.s] = {};
|
|
||||||
}
|
|
||||||
importObj[build.s][build.n] = build.c;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
throw { type: 'bad-comparison', message: e.message, details: e };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let facets = [];
|
|
||||||
let selectedLength = defaultFacets.length;
|
|
||||||
let selectedFacets = new Array(selectedLength);
|
|
||||||
|
|
||||||
for (let i = 0; i < ShipFacets.length; i++) {
|
|
||||||
let facet = Object.assign({ }, ShipFacets[i]);
|
|
||||||
let defaultIndex = defaultFacets.indexOf(facet.i);
|
|
||||||
if(defaultIndex == -1) {
|
|
||||||
facets.push(facet);
|
|
||||||
} else {
|
|
||||||
facet.active = true;
|
|
||||||
selectedFacets[selectedLength - defaultIndex - 1] = facet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
facets = selectedFacets.concat(facets);
|
|
||||||
builds.sort(sortBy(predicate));
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: 'Coriolis EDCD Edition - Compare',
|
|
||||||
predicate,
|
|
||||||
desc,
|
|
||||||
facets,
|
|
||||||
builds,
|
|
||||||
compareMode,
|
|
||||||
code,
|
|
||||||
name,
|
|
||||||
newName,
|
|
||||||
saved,
|
|
||||||
importObj
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Create a Ship instance / build
|
|
||||||
* @param {string} id Ship Id
|
|
||||||
* @param {name} name Build name
|
|
||||||
* @param {string} code Optional - Serialized ship code
|
|
||||||
* @return {Object} Ship instance with build name
|
|
||||||
*/
|
|
||||||
_createBuild(id, name, code) {
|
|
||||||
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
|
|
||||||
|
|
||||||
if (!code) { // No build found
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = Ships[id]; // Get ship properties
|
|
||||||
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
|
|
||||||
b.buildFrom(code); // Populate components from code
|
|
||||||
b.buildName = name;
|
|
||||||
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
|
||||||
return b;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state with the specified sort predicates
|
|
||||||
* @param {String} predicate Sort predicate - property name
|
|
||||||
*/
|
|
||||||
_sortShips(predicate) {
|
|
||||||
let { builds, desc } = this.state;
|
|
||||||
if (this.state.predicate == predicate) {
|
|
||||||
desc = !desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
builds.sort(sortBy(predicate));
|
|
||||||
|
|
||||||
if (desc) {
|
|
||||||
builds.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ predicate, desc });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show selected builds modal
|
|
||||||
*/
|
|
||||||
_selectBuilds() {
|
|
||||||
this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update selected builds with new list
|
|
||||||
* @param {Array} newBuilds List of new builds
|
|
||||||
*/
|
|
||||||
_buildsSelected(newBuilds) {
|
|
||||||
this.context.hideModal();
|
|
||||||
let builds = [];
|
|
||||||
|
|
||||||
for (let b of newBuilds) {
|
|
||||||
builds.push(this._createBuild(b.id, b.buildName));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ builds, saved: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle facet display
|
|
||||||
* @param {string} facet Facet / Ship Property
|
|
||||||
*/
|
|
||||||
_toggleFacet(facet) {
|
|
||||||
facet.active = !facet.active;
|
|
||||||
this.setState({ facets: [].concat(this.state.facets), saved: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle facet drag
|
|
||||||
* @param {Event} e Drag Event
|
|
||||||
*/
|
|
||||||
_facetDrag(e) {
|
|
||||||
this.nodeAfter = false;
|
|
||||||
this.dragged = e.currentTarget;
|
|
||||||
let placeholder = this.placeholder = document.createElement('li');
|
|
||||||
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
|
|
||||||
placeholder.className = 'facet-placeholder';
|
|
||||||
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
|
|
||||||
e.dataTransfer.effectAllowed = 'move';
|
|
||||||
e.dataTransfer.setData('text/html', e.currentTarget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle facet drop
|
|
||||||
* @param {Event} e Drop Event
|
|
||||||
*/
|
|
||||||
_facetDrop(e) {
|
|
||||||
this.dragged.parentNode.removeChild(this.placeholder);
|
|
||||||
let facets = this.state.facets;
|
|
||||||
let frm = Number(this.dragged.dataset.i);
|
|
||||||
let to = Number(this.over.dataset.i);
|
|
||||||
|
|
||||||
if (frm < to) {
|
|
||||||
to--;
|
|
||||||
}
|
|
||||||
if (this.nodeAfter) {
|
|
||||||
to++;
|
|
||||||
}
|
|
||||||
|
|
||||||
facets.splice(to, 0, facets.splice(frm, 1)[0]);
|
|
||||||
this.dragged.style.display = null;
|
|
||||||
this.setState({ facets: [].concat(facets), saved: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle facet drag over
|
|
||||||
* @param {Event} e Drag over Event
|
|
||||||
*/
|
|
||||||
_facetDragOver(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if(e.target.className == 'facet-placeholder') {
|
|
||||||
return;
|
|
||||||
} else if (e.target != e.currentTarget) {
|
|
||||||
this.over = e.target;
|
|
||||||
this.dragged.style.display = 'none';
|
|
||||||
let relX = e.clientX - this.over.getBoundingClientRect().left;
|
|
||||||
let width = this.over.offsetWidth / 2;
|
|
||||||
let parent = e.target.parentNode;
|
|
||||||
|
|
||||||
if (parent == e.currentTarget) {
|
|
||||||
if(relX > width && this.dragged != e.target) {
|
|
||||||
this.nodeAfter = true;
|
|
||||||
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
|
|
||||||
} else {
|
|
||||||
this.nodeAfter = false;
|
|
||||||
parent.insertBefore(this.placeholder, e.target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Handle name change and update state
|
|
||||||
* @param {SyntheticEvent} e Event
|
|
||||||
*/
|
|
||||||
_onNameChange(e) {
|
|
||||||
this.setState({ newName: e.target.value, saved: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete the current comparison
|
|
||||||
*/
|
|
||||||
_delete() {
|
|
||||||
Persist.deleteComparison(this.state.name);
|
|
||||||
Router.go('/compare');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Import the comparison builds
|
|
||||||
*/
|
|
||||||
_import() {
|
|
||||||
let builds = {};
|
|
||||||
|
|
||||||
for (let ship of this.state.builds) {
|
|
||||||
if (!builds[ship.id]) {
|
|
||||||
builds[ship.id] = {};
|
|
||||||
}
|
|
||||||
builds[ship.id][ship.buildName] = ship.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.context.showModal(<ModalImport builds={builds} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the current comparison
|
|
||||||
*/
|
|
||||||
_save() {
|
|
||||||
let { newName, builds, facets } = this.state;
|
|
||||||
let selectedFacets = [];
|
|
||||||
|
|
||||||
facets.forEach((f) => {
|
|
||||||
if (f.active) {
|
|
||||||
selectedFacets.unshift(f.i);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Persist.saveComparison(newName, builds, selectedFacets);
|
|
||||||
Router.replace(`/compare/${encodeURIComponent(this.state.newName)}`);
|
|
||||||
this.setState({ name: newName, saved: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize and generate a long URL for the current comparison
|
|
||||||
* @return {string} URL for serialized comparison
|
|
||||||
*/
|
|
||||||
_buildUrl() {
|
|
||||||
let { facets, builds, name, predicate, desc } = this.state;
|
|
||||||
let selectedFacets = [];
|
|
||||||
|
|
||||||
for (let f of facets) {
|
|
||||||
if (f.active) {
|
|
||||||
selectedFacets.unshift(f.i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
|
|
||||||
let loc = window.location;
|
|
||||||
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the long permalink URL
|
|
||||||
*/
|
|
||||||
_genPermalink() {
|
|
||||||
this.context.showModal(<ModalPermalink url={this._buildUrl()}/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate E:D Forum BBCode and show in the export modal
|
|
||||||
*/
|
|
||||||
_genBBcode() {
|
|
||||||
let { translate, formats } = this.context.language;
|
|
||||||
let { facets, builds } = this.state;
|
|
||||||
|
|
||||||
let generator = (callback) => {
|
|
||||||
let url = this._buildUrl();
|
|
||||||
ShortenUrl(url,
|
|
||||||
(shortenedUrl) => callback(comparisonBBCode(translate, formats, facets, builds, shortenedUrl)),
|
|
||||||
(error) => callback(comparisonBBCode(translate, formats, facets, builds, url))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.context.showModal(<ModalExport
|
|
||||||
title={translate('forum') + ' BBCode'}
|
|
||||||
generator={generator}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update dimenions from rendered DOM
|
|
||||||
*/
|
|
||||||
_updateDimensions() {
|
|
||||||
this.setState({
|
|
||||||
chartWidth: this.chartRef.offsetWidth
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update all ship costs on disount change
|
|
||||||
*/
|
|
||||||
_updateDiscounts() {
|
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
|
||||||
let builds = [];
|
|
||||||
|
|
||||||
for (let b of this.state.builds) {
|
|
||||||
builds.push(b.applyDiscounts(shipDiscount, moduleDiscount));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ builds });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state based on context changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
|
||||||
this.setState(this._initState(nextContext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add listeners when about to mount
|
|
||||||
*/
|
|
||||||
componentWillMount() {
|
|
||||||
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
|
|
||||||
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger DOM updates on mount
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
this._updateDimensions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove listeners on unmount
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.resizeListener.remove();
|
|
||||||
this.persistListener.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the Page
|
|
||||||
* @return {React.Component} The page contents
|
|
||||||
*/
|
|
||||||
renderPage() {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
let compareHeader;
|
|
||||||
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
|
|
||||||
|
|
||||||
if (this.state.compareMode) {
|
|
||||||
compareHeader = <tr>
|
|
||||||
<td className='head'>{translate('comparison')}</td>
|
|
||||||
<td>
|
|
||||||
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
|
|
||||||
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
|
|
||||||
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
|
|
||||||
</button>
|
|
||||||
<button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
|
|
||||||
<button onClick={this._selectBuilds}>
|
|
||||||
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
|
|
||||||
</button>
|
|
||||||
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
|
|
||||||
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
|
|
||||||
</button>
|
|
||||||
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
|
|
||||||
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>;
|
|
||||||
} else {
|
|
||||||
compareHeader = <tr>
|
|
||||||
<td className='head'>{translate('comparison')}</td>
|
|
||||||
<td>
|
|
||||||
<h3>{name}</h3>
|
|
||||||
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
|
|
||||||
</td>
|
|
||||||
</tr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
|
|
||||||
<table id='comparison'>
|
|
||||||
<tbody>
|
|
||||||
{compareHeader}
|
|
||||||
<tr key='facets'>
|
|
||||||
<td className='head'>{translate('compare')}</td>
|
|
||||||
<td>
|
|
||||||
<ul id='facet-container' onDragOver={this._facetDragOver}>
|
|
||||||
{facets.map((f, i) =>
|
|
||||||
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onClick={this._toggleFacet.bind(this, f)}>
|
|
||||||
{'↔ ' + translate(f.title)}
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
|
|
||||||
|
|
||||||
{!builds.length ?
|
|
||||||
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
|
|
||||||
facets.filter((f) => f.active).map((f, i) =>
|
|
||||||
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
|
|
||||||
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
|
|
||||||
<BarChart
|
|
||||||
width={chartWidth}
|
|
||||||
data={builds}
|
|
||||||
properties={f.props}
|
|
||||||
labels={f.lbls}
|
|
||||||
unit={translate(f.unit)}
|
|
||||||
format={f.fmt}
|
|
||||||
title={translate(f.title)}
|
|
||||||
predicate={predicate}
|
|
||||||
desc={desc}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
|
|||||||
* Unexpected Error page / block
|
* Unexpected Error page / block
|
||||||
*/
|
*/
|
||||||
export default class ErrorDetails extends React.Component {
|
export default class ErrorDetails extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
route: PropTypes.object.isRequired,
|
route: PropTypes.object.isRequired,
|
||||||
language: PropTypes.object.isRequired
|
language: PropTypes.object.isRequired
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user