mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 06:43:24 +00:00
Compare commits
615 Commits
2.9.1
...
83f1f9aa2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83f1f9aa2e | ||
|
|
dee14a5dee | ||
|
|
db13da95db | ||
|
|
cb08b10a63 | ||
|
|
b9abf784f4 | ||
|
|
39287bc5d7 | ||
|
|
bcdd0c6044 | ||
|
|
f70455ce26 | ||
|
|
888f807a7b | ||
|
|
5040141096 | ||
|
|
46ba782911 | ||
|
|
524e204e01 | ||
|
|
a9753828c1 | ||
|
|
6d30a54925 | ||
|
|
7c58eb1cde | ||
|
|
4001e1e9ac | ||
|
|
0da00d38a4 | ||
|
|
1db6fe1a34 | ||
|
|
10b8bb95a9 | ||
|
|
8d9581900f | ||
|
|
2bb55d2c36 | ||
|
|
49c827b2c8 | ||
|
|
cf50537e3d | ||
|
|
804466f88a | ||
|
|
bded793374 | ||
|
|
fc918d893c | ||
|
|
5fe13b26a4 | ||
|
|
be1393994e | ||
|
|
dc6db31d43 | ||
|
|
840ce9f3e4 | ||
|
|
9674aa2367 | ||
|
|
d19b6b107f | ||
|
|
1b96c18ecb | ||
|
|
0f43c4d7eb | ||
|
|
4c70806a5a | ||
|
|
7de304bdbe | ||
|
|
34f9f28c16 | ||
|
|
13ec027772 | ||
|
|
3966f92454 | ||
|
|
38f72438dd | ||
|
|
0179382379 | ||
|
|
7f5c652f49 | ||
|
|
1f9b1e5d27 | ||
|
|
ebf4491901 | ||
|
|
d322a47592 | ||
|
|
06a58d22cb | ||
|
|
25d4520eee | ||
|
|
0087062468 | ||
|
|
14bb49a2bc | ||
|
|
ab671b0af5 | ||
|
|
304ddf9ea8 | ||
|
|
b3f320e69f | ||
|
|
3a63e08f80 | ||
|
|
a3feb42fd7 | ||
|
|
a77d991cf9 | ||
|
|
9ebe5dc786 | ||
|
|
baace95f83 | ||
|
|
fc5db94f9a | ||
|
|
c3b0e8d949 | ||
|
|
1b8c460876 | ||
|
|
67409a613b | ||
|
|
e4a826592f | ||
|
|
cee4c32551 | ||
|
|
081d8fb86a | ||
|
|
3dfd563d90 | ||
|
|
fd08cd219c | ||
|
|
6a15326d31 | ||
|
|
608ecc51b7 | ||
|
|
fcef26ebbb | ||
|
|
ba6d758ed5 | ||
|
|
43aa3e4e79 | ||
|
|
18f0e060a7 | ||
|
|
c7547e8baf | ||
|
|
ffff242abe | ||
|
|
b44c66b986 | ||
|
|
ae77ec6256 | ||
|
|
4f1e32b154 | ||
|
|
af37c2bfc5 | ||
|
|
5d4ab6f2ad | ||
|
|
0c9db53057 | ||
|
|
b689605ac2 | ||
|
|
baab91e371 | ||
|
|
70e69c7099 | ||
|
|
f4534fd3eb | ||
|
|
93594e1a65 | ||
|
|
b5e449ea54 | ||
|
|
0ff4b849aa | ||
|
|
b99e38043f | ||
|
|
28e3a59473 | ||
|
|
b20290fb60 | ||
|
|
2734beb6f8 | ||
|
|
345eec528c | ||
|
|
7a17e18a76 | ||
|
|
4697677457 | ||
|
|
7d8a5a1368 | ||
|
|
dd7402bd0e | ||
|
|
65592b0fc6 | ||
|
|
0ab66023a6 | ||
|
|
d6fad098ee | ||
|
|
1b5730d337 | ||
|
|
439b615b1b | ||
|
|
a8b30594dc | ||
|
|
9b6b1d328c | ||
|
|
ac2e2e4d69 | ||
|
|
3a5fb31860 | ||
|
|
c610eb8627 | ||
|
|
94980270c4 | ||
|
|
c685e002e3 | ||
|
|
1f665eed9e | ||
|
|
0c4fc1fd9a | ||
|
|
0fc033363e | ||
|
|
fb6e9538bc | ||
|
|
95b98fc4ed | ||
|
|
1840dafed0 | ||
|
|
1ad82b116c | ||
|
|
7bdd17504b | ||
|
|
2d820bb5d5 | ||
|
|
2e14512ed8 | ||
|
|
48ed583c6d | ||
|
|
dd444a17f3 | ||
|
|
2ea63c711e | ||
|
|
6d6d31db25 | ||
|
|
e9273dcb9b | ||
|
|
2bdc4562c6 | ||
|
|
9e8a5323e9 | ||
|
|
8e001063b3 | ||
|
|
dc88fab4c5 | ||
|
|
dfca917e50 | ||
|
|
ef7dfd6ca1 | ||
|
|
435c1b6d45 | ||
|
|
c5c9abe588 | ||
|
|
363735d36b | ||
|
|
2741e7701b | ||
|
|
3be442ea60 | ||
|
|
34cbeca201 | ||
|
|
b37e73ead6 | ||
|
|
ee775521d6 | ||
|
|
5f84aaef1b | ||
|
|
99ac58d999 | ||
|
|
f128a1e87d | ||
|
|
8c0768b451 | ||
|
|
319307136c | ||
|
|
a498452943 | ||
|
|
4b854b8305 | ||
|
|
b400db8216 | ||
|
|
fb811faf5e | ||
|
|
deeb525433 | ||
|
|
1cb88115f6 | ||
|
|
a181791500 | ||
|
|
94eec120da | ||
|
|
48092d4395 | ||
|
|
2457c30b94 | ||
|
|
593f069806 | ||
|
|
a073692632 | ||
|
|
7752d5c9db | ||
|
|
544e5acaef | ||
|
|
9ab35bbaf9 | ||
|
|
98782da200 | ||
|
|
2936364934 | ||
|
|
01e1609a9f | ||
|
|
f85a03a9ae | ||
|
|
2703c2aa23 | ||
|
|
954921c231 | ||
|
|
8bed35a8ba | ||
|
|
9f4ae60577 | ||
|
|
ee3c50e27d | ||
|
|
03020743b3 | ||
|
|
001fed67b7 | ||
|
|
3894915740 | ||
|
|
68fd13e8dc | ||
|
|
fdf16cd959 | ||
|
|
d916c67fe0 | ||
|
|
d8a8e224f4 | ||
|
|
e1c115747c | ||
|
|
e9b6d71606 | ||
|
|
e03e249d2f | ||
|
|
0cfb0b6878 | ||
|
|
600df162aa | ||
|
|
94141aa3c5 | ||
|
|
aca90d7077 | ||
|
|
a66fa8e83f | ||
|
|
194db07057 | ||
|
|
307886d4ae | ||
|
|
bbba048129 | ||
|
|
222173b388 | ||
|
|
ec0d05e081 | ||
|
|
b3be0bd639 | ||
|
|
529d80682c | ||
|
|
934de01803 | ||
|
|
3367580d78 | ||
|
|
fbf59219d0 | ||
|
|
77401a3b3f | ||
|
|
7c587c29aa | ||
|
|
2295dccd82 | ||
|
|
cc4ad6d132 | ||
|
|
8a2d27290a | ||
|
|
eda61a8e06 | ||
|
|
3987c4e681 | ||
|
|
71b90eb6f4 | ||
|
|
4e891f382c | ||
|
|
3d3f9e44b5 | ||
|
|
dd7a133caa | ||
|
|
ae247c4812 | ||
|
|
6c932f96a6 | ||
|
|
0ea25692d3 | ||
|
|
e129e1da39 | ||
|
|
8acd32b0fc | ||
|
|
8e5dd9fb8d | ||
|
|
97ce2828e0 | ||
|
|
f8f99a5aaa | ||
|
|
70cfa58896 | ||
|
|
6e79ced51e | ||
|
|
56571f9c1f | ||
|
|
0b10cac85c | ||
|
|
34c04a6354 | ||
|
|
4e337c4ca1 | ||
|
|
5048b7e094 | ||
|
|
27fbc1ad66 | ||
|
|
4ab376d9ed | ||
|
|
dfffc3a268 | ||
|
|
b59fa15e00 | ||
|
|
12bca4c44e | ||
|
|
2858ef3e93 | ||
|
|
7d99471f89 | ||
|
|
a2ab708ac9 | ||
|
|
a34a9c355f | ||
|
|
3215b3942d | ||
|
|
557c0afd9b | ||
|
|
d52365a204 | ||
|
|
14b2a14e58 | ||
|
|
7f24904f77 | ||
|
|
da07790594 | ||
|
|
5008c7cd74 | ||
|
|
a778b1b6e1 | ||
|
|
bd9771f9ba | ||
|
|
600c244f9b | ||
|
|
a599b1a076 | ||
|
|
3e0a5e22b1 | ||
|
|
3a6ac818c2 | ||
|
|
6f077d4c41 | ||
|
|
9c767c928c | ||
|
|
515f4ad3da | ||
|
|
4fcf074595 | ||
|
|
e5f8153a34 | ||
|
|
571854a11c | ||
|
|
1f22f249a1 | ||
|
|
718ac0a514 | ||
|
|
8f089cb1ee | ||
|
|
d19a7276dd | ||
|
|
10fffe67fc | ||
|
|
f0bf8e8ce2 | ||
|
|
598cf8d677 | ||
|
|
90f03de3fe | ||
|
|
e0766f4424 | ||
|
|
28a90768e4 | ||
|
|
f3d917ccbe | ||
|
|
7e5d52385d | ||
|
|
4368015dc0 | ||
|
|
1201da1811 | ||
|
|
d195b568b0 | ||
|
|
c9866c146b | ||
|
|
5d52809d0d | ||
|
|
8f0cca4fd9 | ||
|
|
e46bb425fe | ||
|
|
06dc110025 | ||
|
|
e9c34c636a | ||
|
|
59d38cbd33 | ||
|
|
51f5188efc | ||
|
|
be8934da80 | ||
|
|
18d78b3089 | ||
|
|
b1ff4e84f7 | ||
|
|
bed2ede701 | ||
|
|
124bd62d2c | ||
|
|
975846f4ab | ||
|
|
3f73f9be10 | ||
|
|
d218101708 | ||
|
|
608ce12156 | ||
|
|
80d653483a | ||
|
|
f9a1e5afd9 | ||
|
|
7f5181a9c7 | ||
|
|
93d8f15f26 | ||
|
|
06d8930777 | ||
|
|
23f4024e09 | ||
|
|
a5e9a71037 | ||
|
|
423d42bc8a | ||
|
|
4075bab3d0 | ||
|
|
469f7a0a48 | ||
|
|
25d9f8ec61 | ||
|
|
1151bd1614 | ||
|
|
2456ce330b | ||
|
|
f86ce62c27 | ||
|
|
c295a9f4e4 | ||
|
|
c4186faa4a | ||
|
|
00f4889c93 | ||
|
|
b6c83f4aba | ||
|
|
3fa040c210 | ||
|
|
8c4957c21e | ||
|
|
a178c59aa3 | ||
|
|
0d9fcd97d7 | ||
|
|
b02ca117be | ||
|
|
ca2e2a22dd | ||
|
|
035e217124 | ||
|
|
6424ab283a | ||
|
|
f30f10c904 | ||
|
|
8ce09a607f | ||
|
|
5b48e30798 | ||
|
|
0d7e6af2e6 | ||
|
|
be3ca23aee | ||
|
|
28f464ba5a | ||
|
|
8cedd1b8bc | ||
|
|
56cbc49d04 | ||
|
|
5d1ccd2a20 | ||
|
|
f898fbc55e | ||
|
|
63acad3aeb | ||
|
|
5b4b0b8dff | ||
|
|
8492dd74f9 | ||
|
|
77e652cd34 | ||
|
|
5069d7e464 | ||
|
|
8b3e9c0f63 | ||
|
|
99e76e480b | ||
|
|
c161ed2298 | ||
|
|
464770f096 | ||
|
|
9fba1be814 | ||
|
|
b419b8b104 | ||
|
|
0cae955ca2 | ||
|
|
ec70ad5d29 | ||
|
|
8a386c4ece | ||
|
|
013460ada4 | ||
|
|
d46ad89dc5 | ||
|
|
86c53c8e46 | ||
|
|
df14786e79 | ||
|
|
24f206ad82 | ||
|
|
242c3efe45 | ||
|
|
07a848b906 | ||
|
|
1b5402fd2d | ||
|
|
903d791549 | ||
|
|
f03c164f1c | ||
|
|
107bab0192 | ||
|
|
41f5ebb2f1 | ||
|
|
266090dc2a | ||
|
|
83f29f3d62 | ||
|
|
3e6137ca96 | ||
|
|
58510bbd22 | ||
|
|
2f7be75bcf | ||
|
|
0aecbbf892 | ||
|
|
9ef2f4179b | ||
|
|
afbfe3ea12 | ||
|
|
b145a2da85 | ||
|
|
a679f7dd98 | ||
|
|
f343366ea1 | ||
|
|
1bbea7dda0 | ||
|
|
da097e0955 | ||
|
|
1307474755 | ||
|
|
80f5a95297 | ||
|
|
e619966679 | ||
|
|
3f94382925 | ||
|
|
ea7a6f8872 | ||
|
|
4e87b3a0b8 | ||
|
|
8463dd46f7 | ||
|
|
3febe465f6 | ||
|
|
45b834c424 | ||
|
|
1ca041097b | ||
|
|
587f7acd5b | ||
|
|
9e64e4a26b | ||
|
|
96e09ab36c | ||
|
|
4268d3f07b | ||
|
|
1767a2aed5 | ||
|
|
9f061506bb | ||
|
|
0eb9a2048d | ||
|
|
faeafb2402 | ||
|
|
2a0acfa6cb | ||
|
|
0ac44ac267 | ||
|
|
b9ae3a4d5a | ||
|
|
b1aefb0003 | ||
|
|
f6a41ec55c | ||
|
|
39650cc584 | ||
|
|
d837287da7 | ||
|
|
6e718a39d1 | ||
|
|
2e7db02238 | ||
|
|
dbfd108819 | ||
|
|
82c0f6fc0f | ||
|
|
9e6bbaa67d | ||
|
|
012a203c4a | ||
|
|
c7ba129ed7 | ||
|
|
fbe57d00db | ||
|
|
dc4fdf215c | ||
|
|
2166ac1584 | ||
|
|
504756de09 | ||
|
|
90f04dba94 | ||
|
|
bfd7645fb7 | ||
|
|
c37c6983c3 | ||
|
|
736c700aa0 | ||
|
|
de1cb901fc | ||
|
|
3d9d6397f6 | ||
|
|
dbe836729f | ||
|
|
82fa93e676 | ||
|
|
90a3392b80 | ||
|
|
b7f715bd5e | ||
|
|
5f88f46770 | ||
|
|
2244c91a64 | ||
|
|
78134404c3 | ||
|
|
c9cd6b175d | ||
|
|
5f70d283e0 | ||
|
|
cd1e27fd11 | ||
|
|
0fd1c19514 | ||
|
|
a82dffd77d | ||
|
|
d3eed87077 | ||
|
|
d99f8ad7e7 | ||
|
|
e933305407 | ||
|
|
690bc5a64a | ||
|
|
d2380a5c9c | ||
|
|
c2d0a6e9e0 | ||
|
|
a8c44fddca | ||
|
|
0e6b306bdc | ||
|
|
9b2a47ba0c | ||
|
|
3038ebc1c7 | ||
|
|
5a48d5b400 | ||
|
|
f36edfe98d | ||
|
|
3e80d6e13b | ||
|
|
c8f0a1ccc6 | ||
|
|
79325a3129 | ||
|
|
1ca0fffe5e | ||
|
|
f5b9842b2e | ||
|
|
72ea3f125e | ||
|
|
f7a2408e44 | ||
|
|
c05de9a085 | ||
|
|
fc657f3b2d | ||
|
|
e388de0364 | ||
|
|
26e55afb29 | ||
|
|
9b8e1039f6 | ||
|
|
082d14ba50 | ||
|
|
87ead595c6 | ||
|
|
fb073acdc3 | ||
|
|
3dda4e24bf | ||
|
|
c966bd08ed | ||
|
|
692c73a6d0 | ||
|
|
849b484b4d | ||
|
|
9489baccda | ||
|
|
62cac15222 | ||
|
|
0ee9ce2958 | ||
|
|
a83007fc6c | ||
|
|
901f266dad | ||
|
|
6009426f04 | ||
|
|
6fb89df4cd | ||
|
|
48b99425a3 | ||
|
|
9569f9b09b | ||
|
|
7421896902 | ||
|
|
b837c8d66c | ||
|
|
e180f96eab | ||
|
|
de1f1f790e | ||
|
|
fefa74ce0f | ||
|
|
a2ceb70aa4 | ||
|
|
634103f144 | ||
|
|
f3379de81e | ||
|
|
064ee74c1a | ||
|
|
0e0c15fe3b | ||
|
|
3c855c608d | ||
|
|
414516289c | ||
|
|
d79fa71337 | ||
|
|
39c2ca94c8 | ||
|
|
acf1c5ce04 | ||
|
|
fd7923155f | ||
|
|
2aa6a6daa2 | ||
|
|
9662621980 | ||
|
|
37d007d9ab | ||
|
|
a4e48c359a | ||
|
|
8ef0101a6e | ||
|
|
e21f109026 | ||
|
|
737837eebd | ||
|
|
ba09b54409 | ||
|
|
417091c648 | ||
|
|
5a02368298 | ||
|
|
da7c739497 | ||
|
|
0842281466 | ||
|
|
b480f879b1 | ||
|
|
f8a215d790 | ||
|
|
da69f3b2c8 | ||
|
|
fc442c1a42 | ||
|
|
cf59a6b9fd | ||
|
|
34afcd511a | ||
|
|
e2444a2e4e | ||
|
|
692516de9b | ||
|
|
d51009c823 | ||
|
|
2f121bef5e | ||
|
|
61f7d376d2 | ||
|
|
9e253012e6 | ||
|
|
e4e5b1327b | ||
|
|
6ef3b227b8 | ||
|
|
bf657a0945 | ||
|
|
c3dd1886c9 | ||
|
|
32498bb8a7 | ||
|
|
69bb90a0e4 | ||
|
|
8e2988edf0 | ||
|
|
876a352cfd | ||
|
|
84e44cabfa | ||
|
|
36a838d565 | ||
|
|
9ee8693f40 | ||
|
|
6f02965149 | ||
|
|
27ce82de3b | ||
|
|
3d5a9ef220 | ||
|
|
9b81f6efd2 | ||
|
|
3e77e23d71 | ||
|
|
120c032c82 | ||
|
|
46e15b8ecd | ||
|
|
d71d87041b | ||
|
|
124bd58b9f | ||
|
|
257b9b0562 | ||
|
|
b8e15f691d | ||
|
|
2255e3bfc4 | ||
|
|
8797d84605 | ||
|
|
719179a923 | ||
|
|
1d544099f6 | ||
|
|
9b131a762a | ||
|
|
08c5d2256a | ||
|
|
ed6ee4341f | ||
|
|
157c1148fb | ||
|
|
507ea9e09e | ||
|
|
af68cba7be | ||
|
|
224fbe0e8f | ||
|
|
49a6c5f2c4 | ||
|
|
07c936897c | ||
|
|
3786fb907c | ||
|
|
752d9f0c68 | ||
|
|
baf59aafcb | ||
|
|
8787303d2a | ||
|
|
298eaa8b4b | ||
|
|
118c80af27 | ||
|
|
7b87038a8c | ||
|
|
d103939e45 | ||
|
|
fdc9171c69 | ||
|
|
2a6ade3cab | ||
|
|
c8c42689f9 | ||
|
|
da2f472f4d | ||
|
|
3ba878237b | ||
|
|
7577fb53a2 | ||
|
|
f2509f89ee | ||
|
|
9dd1f78330 | ||
|
|
ebb6c2c420 | ||
|
|
9d23dc1763 | ||
|
|
5fa3f8703e | ||
|
|
797885faea | ||
|
|
68e7e9f5b7 | ||
|
|
d1d165ad51 | ||
|
|
f43bd100e6 | ||
|
|
0ebb247666 | ||
|
|
aa73bc2809 | ||
|
|
c3fcdb918f | ||
|
|
5e3722bcfd | ||
|
|
6a4fca2eb1 | ||
|
|
fbba0e3ea5 | ||
|
|
6922cfd047 | ||
|
|
d93fc1d2d0 | ||
|
|
b15695128f | ||
|
|
48290b2e75 | ||
|
|
b62abef618 | ||
|
|
e0c0778d82 | ||
|
|
b4a82ae7c2 | ||
|
|
fc73102b30 | ||
|
|
b14e7473f3 | ||
|
|
f4cc9fc722 | ||
|
|
8d19ef7783 | ||
|
|
32be186ec5 | ||
|
|
abb0c7f90d | ||
|
|
71ddbdfe75 | ||
|
|
38463ad9a6 | ||
|
|
0870b90443 | ||
|
|
70375f94c8 | ||
|
|
2d4336116a | ||
|
|
f52880765e | ||
|
|
dbfe68decb | ||
|
|
659f337de9 | ||
|
|
1d36d41da1 | ||
|
|
0f90efaa54 | ||
|
|
48ccab152b | ||
|
|
4442930a82 | ||
|
|
912a775088 | ||
|
|
24a229d818 | ||
|
|
66afb61494 | ||
|
|
e7511cc05b | ||
|
|
926f19a936 | ||
|
|
9e6f86b963 | ||
|
|
059de43a9a | ||
|
|
e246b737b2 | ||
|
|
f3bc900f16 | ||
|
|
38842417b0 | ||
|
|
82d485a98e | ||
|
|
fac71feea7 | ||
|
|
ec148847a9 | ||
|
|
445c63878b | ||
|
|
0474af912a | ||
|
|
7df5953824 | ||
|
|
e391b563fb | ||
|
|
53f62f96d0 | ||
|
|
18745979a0 | ||
|
|
c1966a38ff | ||
|
|
d86973f3b1 | ||
|
|
56b8d19649 | ||
|
|
7cb037e0bc | ||
|
|
56e1b3f9e9 | ||
|
|
c792323a8a | ||
|
|
4d2865de13 | ||
|
|
0d360bc367 | ||
|
|
91c9b46b91 | ||
|
|
7e67bd80dd | ||
|
|
2f775ea09b | ||
|
|
0603c55089 | ||
|
|
41f25a44e9 | ||
|
|
35440b7273 | ||
|
|
7d68b91018 | ||
|
|
deaa61b848 | ||
|
|
2d00cbc41b | ||
|
|
57c1e83c67 | ||
|
|
b7079dbd4e | ||
|
|
be642a5373 | ||
|
|
21309e129f |
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
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
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,
|
||||
"classes": true,
|
||||
"modules": true
|
||||
},
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
@@ -33,7 +34,6 @@
|
||||
"ClassDeclaration": true
|
||||
}
|
||||
}],
|
||||
"no-console": 2,
|
||||
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
|
||||
"comma-style": [2, "last"],
|
||||
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,3 +7,7 @@ nginx.pid
|
||||
/bin
|
||||
env
|
||||
*.swp
|
||||
.project
|
||||
.vscode/
|
||||
docs/
|
||||
package-lock.json
|
||||
|
||||
@@ -8,7 +8,8 @@ cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
before_script:
|
||||
before_install:
|
||||
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
|
||||
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.
|
||||
@@ -10,11 +10,6 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
|
||||
|
||||
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
|
||||
|
||||
### Translations
|
||||
|
||||
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
|
||||
These will be merged regularly by the project manager.
|
||||
|
||||
### Feature Requests, Suggestions & Bugs
|
||||
|
||||
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
@@ -23,11 +18,12 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
|
||||
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
|
||||
|
||||
|
||||
### Ship and Module Database
|
||||
|
||||
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
||||
|
||||
You can find hosted and compiled versions of these data-jsons under https://coriolis.io/data/ and https://beta.coriolis.io/data/.
|
||||
You might want to load these as depedency instead of reyling on the npm-dependency.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
{
|
||||
"type_6_transporter": {
|
||||
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
|
||||
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
|
||||
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
|
||||
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101-.Iw18UA==.Aw18UA==.",
|
||||
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101-.Iw18UA==.Aw18UA==.",
|
||||
"Hopper": "A0p0tdFal8d0s8f41717---030302024300--.Iw18UA==.Aw18UA==."
|
||||
},
|
||||
"type_7_transport": {
|
||||
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
|
||||
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
|
||||
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101--.Iw18eQ==.Aw18eQ==.",
|
||||
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000--.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"federal_dropship": {
|
||||
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
|
||||
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201-.Iw18RQ==.Aw18RQ==."
|
||||
},
|
||||
"asp": {
|
||||
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
|
||||
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27-.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"imperial_clipper": {
|
||||
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
|
||||
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
|
||||
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
|
||||
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101-.Iw18WQ==.Aw18WQ==.",
|
||||
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o--.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA.",
|
||||
"Current": "A0patkFflndfskf4-----------------.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA."
|
||||
},
|
||||
"type_9_heavy": {
|
||||
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
|
||||
"Current": "A0patsFklndnsif6---------0706054a0303020224--.AwRj4yo5iA==.EwBhEYy6d6g=."
|
||||
},
|
||||
"python": {
|
||||
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
|
||||
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
|
||||
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
|
||||
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
|
||||
"Cargo": "A0patnFflidsssf5---------050505040448020201-.Iw18eAMQ.Aw18RQ==.",
|
||||
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o-.Iw18eAMQ.IwBhBYy6dkCYRA==.",
|
||||
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201-.Iw1+gDByUA==.EwBhEYy6e0VEA===.",
|
||||
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---00--.Iw18eAMQ.Aw18RQ==."
|
||||
},
|
||||
"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=."
|
||||
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b-.AwRj4yo5dzhA.MwBhCYy6duvARhEA.",
|
||||
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301-.Iw18ZUAxA===.Aw18ZXEA.",
|
||||
"Current": "A0patnFklndksxf5----------------06050505040404-03034524-.Iw18ZUAxA===.Aw18ZXEA.",
|
||||
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2i4524-.AwRj4yVKJ9jCA===.AwhMIyumQRgkA===.",
|
||||
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.Iw18ZUAxA===.Aw18ZXEA."
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
|
||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i----.AwRj4zTZaA==.AwiMIyqo."
|
||||
},
|
||||
"vulture": {
|
||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j--.AwRj4z2Gg===.MwBhBYy6oJmAjLMQ."
|
||||
},
|
||||
"fer_de_lance": {
|
||||
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
|
||||
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27--.Iw18aAMQ.CwBhrSu8EZxEA===."
|
||||
},
|
||||
"eagle": {
|
||||
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
|
||||
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j---.Iw18gDJQ.Aw19kA==."
|
||||
}
|
||||
}
|
||||
|
||||
366
__tests__/fixtures/slef-multiple-builds.json
Normal file
366
__tests__/fixtures/slef-multiple-builds.json
Normal file
@@ -0,0 +1,366 @@
|
||||
[
|
||||
{
|
||||
"header": {
|
||||
"appName": "Inara",
|
||||
"appVersion": "1.0",
|
||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
||||
"appCustomProperties": {
|
||||
"inaraCommanderID": 123,
|
||||
"inaraShipID": 123
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Ship": "krait_mkii",
|
||||
"ShipID": 7,
|
||||
"ShipName": "pancake hammer",
|
||||
"ShipIdent": "PH-01",
|
||||
"HullValue": 44160710,
|
||||
"ModulesValue": 111274094,
|
||||
"Rebuy": 7771743,
|
||||
"Modules": [
|
||||
{
|
||||
"Slot": "largehardpoint1",
|
||||
"Item": "hpt_mininglaser_fixed_small",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "largehardpoint2",
|
||||
"Item": "hpt_cannon_gimbal_large",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_overcharged",
|
||||
"Level": 2,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_auto_loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "largehardpoint3",
|
||||
"Item": "hpt_cannon_gimbal_large",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_overcharged",
|
||||
"Level": 2,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_auto_loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "mediumhardpoint1",
|
||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_highcapacity",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "mediumhardpoint2",
|
||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint1",
|
||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint2",
|
||||
"Item": "hpt_cloudscanner_size0_class3",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint3",
|
||||
"Item": "hpt_shieldbooster_size0_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint4",
|
||||
"Item": "hpt_shieldbooster_size0_class5",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot01_size6",
|
||||
"Item": "int_cargorack_size6_class1",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot02_size6",
|
||||
"Item": "int_cargorack_size6_class1",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot03_size5",
|
||||
"Item": "int_guardianfsdbooster_size5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot04_size5",
|
||||
"Item": "int_fighterbay_size5_class1",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot05_size4",
|
||||
"Item": "int_shieldgenerator_size4_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot06_size3",
|
||||
"Item": "int_dronecontrol_collection_size3_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot07_size3",
|
||||
"Item": "int_dronecontrol_collection_size3_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot08_size2",
|
||||
"Item": "int_refinery_size2_class2",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot09_size1",
|
||||
"Item": "int_dronecontrol_prospector_size1_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "powerplant",
|
||||
"Item": "int_powerplant_size7_class5",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "mainengines",
|
||||
"Item": "int_engine_size6_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "frameshiftdrive",
|
||||
"Item": "int_hyperdrive_size5_class5",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "fsd_longrange",
|
||||
"Level": 2,
|
||||
"Quality": 0.861
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "lifesupport",
|
||||
"Item": "int_lifesupport_size4_class2",
|
||||
"On": true,
|
||||
"Priority": 3
|
||||
},
|
||||
{
|
||||
"Slot": "powerdistributor",
|
||||
"Item": "int_powerdistributor_size7_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "radar",
|
||||
"Item": "int_sensors_size6_class2",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "fueltank",
|
||||
"Item": "int_fueltank_size5_class3",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "armour",
|
||||
"Item": "krait_mkii_armour_grade3",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Engineering": {
|
||||
"BlueprintName": "armour_heavyduty",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"header": {
|
||||
"appName": "Inara",
|
||||
"appVersion": "1.0",
|
||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
||||
"appCustomProperties": {
|
||||
"inaraCommanderID": 123,
|
||||
"inaraShipID": 123
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Ship": "diamondbackxl",
|
||||
"ShipID": 11,
|
||||
"ShipName": "star Hopper",
|
||||
"ShipIdent": "PH-02",
|
||||
"HullValue": 1615649,
|
||||
"ModulesValue": 16981039,
|
||||
"Rebuy": 929837,
|
||||
"Modules": [
|
||||
{
|
||||
"Slot": "tinyhardpoint1",
|
||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||
"On": true,
|
||||
"Value": 3072
|
||||
},
|
||||
{
|
||||
"Slot": "slot01_size4",
|
||||
"Item": "int_fuelscoop_size4_class5",
|
||||
"On": true,
|
||||
"Priority": 3,
|
||||
"Value": 2862364
|
||||
},
|
||||
{
|
||||
"Slot": "slot02_size4",
|
||||
"Item": "int_guardianfsdbooster_size4",
|
||||
"On": true,
|
||||
"Value": 2847499
|
||||
},
|
||||
{
|
||||
"Slot": "slot03_size3",
|
||||
"Item": "int_shieldgenerator_size3_class2",
|
||||
"On": true,
|
||||
"Value": 18812,
|
||||
"Engineering": {
|
||||
"BlueprintName": "shieldgenerator_thermic",
|
||||
"Level": 3,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_shield_health"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "slot04_size3",
|
||||
"Item": "int_repairer_size3_class5",
|
||||
"On": true,
|
||||
"Value": 2302911
|
||||
},
|
||||
{
|
||||
"Slot": "slot05_size2",
|
||||
"Item": "int_buggybay_size2_class2",
|
||||
"On": true,
|
||||
"Priority": 3,
|
||||
"Value": 21600
|
||||
},
|
||||
{
|
||||
"Slot": "slot06_size2",
|
||||
"Item": "int_cargorack_size2_class1",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Value": 2852
|
||||
},
|
||||
{
|
||||
"Slot": "slot07_size1",
|
||||
"Item": "int_supercruiseassist",
|
||||
"On": true,
|
||||
"Priority": 3,
|
||||
"Value": 9121
|
||||
},
|
||||
{
|
||||
"Slot": "slot08_size1",
|
||||
"Item": "int_detailedsurfacescanner_tiny",
|
||||
"On": true,
|
||||
"Value": 250000,
|
||||
"Engineering": {
|
||||
"BlueprintName": "sensor_expanded",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "powerplant",
|
||||
"Item": "int_powerplant_size4_class5",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Value": 1441233,
|
||||
"Engineering": {
|
||||
"BlueprintName": "powerplant_boosted",
|
||||
"Level": 1,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "mainengines",
|
||||
"Item": "int_engine_size4_class5",
|
||||
"On": true,
|
||||
"Value": 1610080,
|
||||
"Engineering": {
|
||||
"BlueprintName": "engine_dirty",
|
||||
"Level": 5,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_engine_lightweight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "frameshiftdrive",
|
||||
"Item": "int_hyperdrive_size5_class5",
|
||||
"On": true,
|
||||
"Value": 5103953,
|
||||
"Engineering": {
|
||||
"BlueprintName": "fsd_longrange",
|
||||
"Level": 5,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_fsd_lightweight"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "lifesupport",
|
||||
"Item": "int_lifesupport_size3_class2",
|
||||
"On": true,
|
||||
"Value": 10133,
|
||||
"Engineering": {
|
||||
"BlueprintName": "misc_lightweight",
|
||||
"Level": 3,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "powerdistributor",
|
||||
"Item": "int_powerdistributor_size4_class5",
|
||||
"On": true,
|
||||
"Value": 389022,
|
||||
"Engineering": {
|
||||
"BlueprintName": "powerdistributor_highfrequency",
|
||||
"Level": 4,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "radar",
|
||||
"Item": "int_sensors_size3_class2",
|
||||
"On": true,
|
||||
"Value": 10133,
|
||||
"Engineering": {
|
||||
"BlueprintName": "sensor_lightweight",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "fueltank",
|
||||
"Item": "int_fueltank_size5_class3",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Value": 97754
|
||||
},
|
||||
{
|
||||
"Slot": "armour",
|
||||
"Item": "diamondbackxl_armour_grade1",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Engineering": {
|
||||
"BlueprintName": "armour_heavyduty",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
8
__tests__/fixtures/slef-multiple-expected-builds.json
Normal file
8
__tests__/fixtures/slef-multiple-expected-builds.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"krait_mkii": {
|
||||
"Imported pancake hammer": "A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ==.AwRgzKIkA===."
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Imported star Hopper": "A0pataFflddfsdf5---02---321P430iv6013w2i.Iw18SQ==.AwRm44GYpKg=."
|
||||
}
|
||||
}
|
||||
188
__tests__/fixtures/slef-single-build.json
Normal file
188
__tests__/fixtures/slef-single-build.json
Normal file
@@ -0,0 +1,188 @@
|
||||
[
|
||||
{
|
||||
"header": {
|
||||
"appName": "Inara",
|
||||
"appVersion": "1.0",
|
||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
||||
"appCustomProperties": {
|
||||
"inaraCommanderID": 123,
|
||||
"inaraShipID": 123
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Ship": "krait_mkii",
|
||||
"ShipID": 7,
|
||||
"ShipName": "pancake hammer",
|
||||
"ShipIdent": "PH-01",
|
||||
"HullValue": 44160710,
|
||||
"ModulesValue": 111274094,
|
||||
"Rebuy": 7771743,
|
||||
"Modules": [
|
||||
{
|
||||
"Slot": "largehardpoint1",
|
||||
"Item": "hpt_mininglaser_fixed_small",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "largehardpoint2",
|
||||
"Item": "hpt_cannon_gimbal_large",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_overcharged",
|
||||
"Level": 2,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_auto_loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "largehardpoint3",
|
||||
"Item": "hpt_cannon_gimbal_large",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_overcharged",
|
||||
"Level": 2,
|
||||
"Quality": 1,
|
||||
"ExperimentalEffect": "special_auto_loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "mediumhardpoint1",
|
||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "weapon_highcapacity",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "mediumhardpoint2",
|
||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint1",
|
||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint2",
|
||||
"Item": "hpt_cloudscanner_size0_class3",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint3",
|
||||
"Item": "hpt_shieldbooster_size0_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "tinyhardpoint4",
|
||||
"Item": "hpt_shieldbooster_size0_class5",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot01_size6",
|
||||
"Item": "int_cargorack_size6_class1",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot02_size6",
|
||||
"Item": "int_cargorack_size6_class1",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "slot03_size5",
|
||||
"Item": "int_guardianfsdbooster_size5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot04_size5",
|
||||
"Item": "int_fighterbay_size5_class1",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot05_size4",
|
||||
"Item": "int_shieldgenerator_size4_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot06_size3",
|
||||
"Item": "int_dronecontrol_collection_size3_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot07_size3",
|
||||
"Item": "int_dronecontrol_collection_size3_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot08_size2",
|
||||
"Item": "int_refinery_size2_class2",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "slot09_size1",
|
||||
"Item": "int_dronecontrol_prospector_size1_class4",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "powerplant",
|
||||
"Item": "int_powerplant_size7_class5",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "mainengines",
|
||||
"Item": "int_engine_size6_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "frameshiftdrive",
|
||||
"Item": "int_hyperdrive_size5_class5",
|
||||
"On": true,
|
||||
"Engineering": {
|
||||
"BlueprintName": "fsd_longrange",
|
||||
"Level": 2,
|
||||
"Quality": 0.861
|
||||
}
|
||||
},
|
||||
{
|
||||
"Slot": "lifesupport",
|
||||
"Item": "int_lifesupport_size4_class2",
|
||||
"On": true,
|
||||
"Priority": 3
|
||||
},
|
||||
{
|
||||
"Slot": "powerdistributor",
|
||||
"Item": "int_powerdistributor_size7_class5",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "radar",
|
||||
"Item": "int_sensors_size6_class2",
|
||||
"On": true
|
||||
},
|
||||
{
|
||||
"Slot": "fueltank",
|
||||
"Item": "int_fueltank_size5_class3",
|
||||
"On": true,
|
||||
"Priority": 1
|
||||
},
|
||||
{
|
||||
"Slot": "armour",
|
||||
"Item": "krait_mkii_armour_grade3",
|
||||
"On": true,
|
||||
"Priority": 1,
|
||||
"Engineering": {
|
||||
"BlueprintName": "armour_heavyduty",
|
||||
"Level": 5,
|
||||
"Quality": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -18,13 +18,13 @@ describe('Import Modal', function() {
|
||||
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()
|
||||
openMenu: jest.fn(),
|
||||
closeMenu: jest.fn(),
|
||||
showModal: jest.fn(),
|
||||
hideModal: jest.fn(),
|
||||
tooltip: jest.fn(),
|
||||
termtip: jest.fn(),
|
||||
onWindowResize: jest.fn()
|
||||
};
|
||||
|
||||
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
|
||||
@@ -110,21 +110,25 @@ describe('Import Modal', function() {
|
||||
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
|
||||
// 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'));
|
||||
|
||||
pasteText(JSON.stringify(importData).replace(/anaconda/g, 'invalid_ship'));
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
|
||||
expect(Object.keys(modal.state.builds)).not.toContain('anaconda');
|
||||
|
||||
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!');
|
||||
expect(Object.keys(modal.state.builds.imperial_clipper).length).toEqual(3);
|
||||
|
||||
pasteText(JSON.stringify(invalidImportData));
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
|
||||
@@ -144,7 +148,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.&bn=Test%20My%20Ship');
|
||||
});
|
||||
|
||||
it('catches an invalid build', function() {
|
||||
@@ -169,7 +173,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.H4sIAAAAAAAAE2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,7 +190,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v6-2i-.AwRj4yvYg%3D%3D%3D.CwRgDBldHn5A.H4sIAAAAAAAAE2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
|
||||
});
|
||||
|
||||
it('imports a valid v4 build with modifications', function() {
|
||||
@@ -198,11 +202,11 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101----.AwRj4zOYg%3D%3D%3D.CwRgDBldLuZA.H4sIAAAAAAAAE12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detaild Builds Array', function() {
|
||||
describe('Import Detailed Builds Array', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
@@ -240,7 +244,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g--.AwRj4zNapI%3D%3D.CwRgDBldUExuBiIlWIA%3D.&bn=Imported%20Federal%20Corvette');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
@@ -252,7 +256,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i--.AwRj4yusg%3D%3D%3D.CwRgDBldHi8IWIA%3D.&bn=Imported%20Beluga%20Liner');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
@@ -264,7 +268,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402--.AwRj4yoo.CwRgDBlVK7HjEA%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
@@ -276,7 +280,7 @@ describe('Import Modal', function() {
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34----2i--.AwRj4yqA.CwRgDMYExrezBig%3D.&bn=Imported%20Cobra%20Mk%20III');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -324,4 +328,41 @@ describe('Import Modal', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Imports SLEF data', () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a single valid SLEF build', () => {
|
||||
const importData = require('./fixtures/slef-single-build.json');
|
||||
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/krait_mkii?code=A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ%3D%3D.AwRgzKIkA%3D%3D%3D.&bn=Imported%20pancake%20hammer');
|
||||
});
|
||||
|
||||
it('imports multiple SLEF builds', () => {
|
||||
const importData = require('./fixtures/slef-multiple-builds.json');
|
||||
const expectedBuilds = require('./fixtures/slef-multiple-expected-builds.json');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(false);
|
||||
clickProceed();
|
||||
expect(modal.state.processed).toBeTruthy();
|
||||
clickImport();
|
||||
|
||||
const builds = Persist.getBuilds();
|
||||
|
||||
for (const shipModel in builds) {
|
||||
for (const buildName in builds[shipModel]) {
|
||||
expect(builds[shipModel][buildName])
|
||||
.toEqual(expectedBuilds[shipModel][buildName]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12852
package-lock.json
generated
12852
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
118
package.json
118
package.json
@@ -1,17 +1,16 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.9.1",
|
||||
"version": "3.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"homepage": "https://coriolis.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
@@ -56,61 +55,92 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"appcache-webpack-plugin": "^1.3.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "*",
|
||||
"babel-jest": "*",
|
||||
"babel-loader": "*",
|
||||
"babel-preset-env": "*",
|
||||
"babel-preset-react": "*",
|
||||
"babel-preset-stage-0": "*",
|
||||
"create-react-class": "^15.6.2",
|
||||
"css-loader": "^0.28.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"d3-selection": "1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"expose-loader": "^0.7.3",
|
||||
"express": "^4.15.2",
|
||||
"extract-text-webpack-plugin": "2.1.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jest-cli": "^21.2.1",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"appcache-webpack-plugin": "^1.4.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"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",
|
||||
"jest-cli": "^23.6.0",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.3",
|
||||
"less": "^3.8.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-measure": "^1.4.7",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-transition-group": "^1.1.2",
|
||||
"react-container-dimensions": "^1.4.1",
|
||||
"react-testutils-additions": "^16.0.0",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "0.41",
|
||||
"rollup-plugin-node-resolve": "3",
|
||||
"style-loader": "^0.16.1",
|
||||
"uglify-js": "^2.4.11",
|
||||
"url-loader": "^0.5.8",
|
||||
"webpack": "^2.4.1",
|
||||
"webpack-dev-server": "^2.4.4",
|
||||
"rollup": "^0.66.2",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"uglify-js": "^3.4.9",
|
||||
"url-loader": "^1.1.1",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-bugsnag-plugins": "^1.2.2",
|
||||
"webpack-cli": "^3.1.1",
|
||||
"webpack-dev-server": "^3.1.9",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"webpack-bugsnag-plugins": "^1.1.1"
|
||||
"workbox-webpack-plugin": "^3.6.1"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"classnames": "^2.2.6",
|
||||
"coriolis-data": "../coriolis-data",
|
||||
"d3": "4.8.0",
|
||||
"detect-browser": "^1.7.0",
|
||||
"d3": "^5.7.0",
|
||||
"detect-browser": "^3.0.1",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.11",
|
||||
"lz-string": "^1.4.4",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.5.8",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-extras": "^0.7.1",
|
||||
"react-fuzzy": "^0.5.2",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^0.22.3",
|
||||
"superagent": "^3.5.2"
|
||||
"recharts": "^1.2.0",
|
||||
"register-service-worker": "^1.5.2",
|
||||
"superagent": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Router from './Router';
|
||||
import { register } from 'register-service-worker';
|
||||
import { EventEmitter } from 'fbemitter';
|
||||
import { getLanguage } from './i18n/Language';
|
||||
import Persist from './stores/Persist';
|
||||
|
||||
import Announcement from './components/Announcement';
|
||||
import Header from './components/Header';
|
||||
import Tooltip from './components/Tooltip';
|
||||
import ModalExport from './components/ModalExport';
|
||||
@@ -13,7 +15,6 @@ import ModalImport from './components/ModalImport';
|
||||
import ModalPermalink from './components/ModalPermalink';
|
||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
||||
import * as JournalUtils from './utils/JournalUtils';
|
||||
|
||||
import AboutPage from './pages/AboutPage';
|
||||
import NotFoundPage from './pages/NotFoundPage';
|
||||
import OutfittingPage from './pages/OutfittingPage';
|
||||
@@ -21,13 +22,14 @@ import ComparisonPage from './pages/ComparisonPage';
|
||||
import ShipyardPage from './pages/ShipyardPage';
|
||||
import ErrorDetails from './pages/ErrorDetails';
|
||||
|
||||
|
||||
const zlib = require('pako');
|
||||
const request = require('superagent');
|
||||
|
||||
/**
|
||||
* Coriolis App
|
||||
*/
|
||||
export default class Coriolis extends React.Component {
|
||||
|
||||
static childContextTypes = {
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
hideModal: PropTypes.func.isRequired,
|
||||
@@ -66,11 +68,11 @@ export default class Coriolis extends React.Component {
|
||||
this.state = {
|
||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||
page: null,
|
||||
announcements: [],
|
||||
language: getLanguage(Persist.getLangCode()),
|
||||
route: {},
|
||||
sizeRatio: Persist.getSizeRatio()
|
||||
};
|
||||
|
||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||
Router('/import?', (r) => this._importBuild(r));
|
||||
Router('/import/:data', (r) => this._importBuild(r));
|
||||
@@ -93,17 +95,32 @@ export default class Coriolis extends React.Component {
|
||||
// Need to decode and gunzip the data, then build the ship
|
||||
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
|
||||
const json = JSON.parse(data);
|
||||
console.log('Ship import data: ');
|
||||
console.log(json);
|
||||
let ship;
|
||||
if (json && json.modules) {
|
||||
ship = CompanionApiUtils.shipFromJson(json);
|
||||
} else if (json && json.Modules) {
|
||||
ship = JournalUtils.shipFromLoadoutJSON(json);
|
||||
console.info('Ship import data: ');
|
||||
console.info(json);
|
||||
let ship, importString;
|
||||
if (json) {
|
||||
if (json.length && json[0].data) { // SLEF
|
||||
if (json.length > 1) { // Multiple builds, open modal
|
||||
importString = data;
|
||||
} else { // Single build, import directly
|
||||
ship = JournalUtils.shipFromLoadoutJSON(json[0].data);
|
||||
}
|
||||
} else { // not SLEF
|
||||
if (json.modules) {
|
||||
ship = CompanionApiUtils.shipFromJson(json);
|
||||
} else if (json.Modules) {
|
||||
ship = JournalUtils.shipFromLoadoutJSON(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ship) {
|
||||
r.params.ship = ship.id;
|
||||
r.params.code = ship.toString();
|
||||
this._setPage(OutfittingPage, r);
|
||||
} else if (importString) {
|
||||
this._setPage(ShipyardPage, r);
|
||||
this._showModal(<ModalImport importString={data}/>);
|
||||
}
|
||||
r.params.ship = ship.id;
|
||||
r.params.code = ship.toString();
|
||||
this._setPage(OutfittingPage, r);
|
||||
} catch (err) {
|
||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||
}
|
||||
@@ -134,9 +151,9 @@ export default class Coriolis extends React.Component {
|
||||
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
||||
if (errObj) {
|
||||
if (errObj instanceof Error) {
|
||||
Bugsnag.notifyException(errObj) // eslint-disable-line
|
||||
bugsnagClient.notify(errObj); // eslint-disable-line
|
||||
} else if (errObj instanceof String) {
|
||||
Bugsnag.notify(msg, errObj) // eslint-disable-line
|
||||
bugsnagClient.notify(msg, errObj); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@@ -180,13 +197,13 @@ export default class Coriolis extends React.Component {
|
||||
case 72: // 'h'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalHelp />);
|
||||
this._showModal(<ModalHelp/>);
|
||||
}
|
||||
break;
|
||||
case 73: // 'i'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalImport />);
|
||||
this._showModal(<ModalImport/>);
|
||||
}
|
||||
break;
|
||||
case 79: // 'o'
|
||||
@@ -208,7 +225,7 @@ export default class Coriolis extends React.Component {
|
||||
* @param {React.Component} content Modal Content
|
||||
*/
|
||||
_showModal(content) {
|
||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
|
||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>;
|
||||
this.setState({ modal });
|
||||
}
|
||||
|
||||
@@ -286,7 +303,7 @@ export default class Coriolis extends React.Component {
|
||||
return this.emitter.addListener('windowResize', listener);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Add a listener to global commands such as save,
|
||||
* @param {Function} listener Listener callback
|
||||
* @return {Object} Subscription token
|
||||
@@ -322,14 +339,50 @@ export default class Coriolis extends React.Component {
|
||||
*/
|
||||
componentWillMount() {
|
||||
// Listen for appcache updated event, present refresh to update view
|
||||
if (window.applicationCache) {
|
||||
window.applicationCache.addEventListener('updateready', () => {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
|
||||
}
|
||||
// Check that service workers are registered
|
||||
if (navigator.storage && navigator.storage.persist) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.storage.persist().then(granted => {
|
||||
if (granted)
|
||||
console.log('Storage will not be cleared except by explicit user action');
|
||||
else
|
||||
console.log('Storage may be cleared by the UA under storage pressure.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
// Your service-worker.js *must* be located at the top-level directory relative to your site.
|
||||
// It won't be able to control pages unless it's located at the same level or higher than them.
|
||||
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
|
||||
// See https://github.com/slightlyoff/ServiceWorker/issues/468
|
||||
const self = this;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register('/service-worker.js', {
|
||||
ready(registration) {
|
||||
console.log('Service worker is active.');
|
||||
},
|
||||
registered(registration) {
|
||||
console.log('Service worker has been registered.');
|
||||
},
|
||||
cached(registration) {
|
||||
console.log('Content has been cached for offline use.');
|
||||
},
|
||||
updatefound(registration) {
|
||||
console.log('New content is downloading.');
|
||||
},
|
||||
updated(registration) {
|
||||
self.setState({ appCacheUpdate: true });
|
||||
console.log('New content is available; please refresh.');
|
||||
},
|
||||
offline() {
|
||||
console.log('No internet connection found. App is running in offline mode.');
|
||||
},
|
||||
error(error) {
|
||||
console.error('Error during service worker registration:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
window.onerror = this._onError.bind(this);
|
||||
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
|
||||
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
|
||||
@@ -346,15 +399,27 @@ export default class Coriolis extends React.Component {
|
||||
*/
|
||||
render() {
|
||||
let currentMenu = this.state.currentMenu;
|
||||
|
||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
|
||||
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
|
||||
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
|
||||
{ this.state.modal }
|
||||
{ this.state.tooltip }
|
||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
|
||||
className={this.state.noTouch ? 'no-touch' : null}>
|
||||
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
||||
currentMenu={currentMenu}/>
|
||||
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
|
||||
text={a.text}/>)}</div>
|
||||
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
||||
<NotFoundPage/>}
|
||||
{this.state.modal}
|
||||
{this.state.tooltip}
|
||||
<footer>
|
||||
|
||||
<div className="right cap">
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
|
||||
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<br/>
|
||||
<a
|
||||
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
|
||||
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
|
||||
since last release
|
||||
({window.CORIOLIS_DATE})</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>;
|
||||
|
||||
@@ -72,6 +72,7 @@ Router.go = function(path, state) {
|
||||
gaTrack(path);
|
||||
let ctx = new Context(path, state);
|
||||
Router.dispatch(ctx);
|
||||
|
||||
if (!ctx.unhandled) {
|
||||
if (isStandAlone()) {
|
||||
Persist.setState(ctx);
|
||||
@@ -257,9 +258,8 @@ Route.prototype.match = function(path, params) {
|
||||
* @param {string} path Path to track
|
||||
*/
|
||||
function gaTrack(path) {
|
||||
if (window.ga) {
|
||||
window.ga('send', 'pageview', path);
|
||||
}
|
||||
const _paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@ function isActive(href) {
|
||||
* Active Link - Highlighted when URL matches window location
|
||||
*/
|
||||
export default class ActiveLink extends Link {
|
||||
|
||||
/**
|
||||
* Renders the component
|
||||
* @return {React.Component} The active link
|
||||
@@ -29,5 +28,4 @@ export default class ActiveLink extends Link {
|
||||
|
||||
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
31
src/app/components/Announcement.jsx
Normal file
31
src/app/components/Announcement.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
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>;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import FuzzySearch from 'react-fuzzy';
|
||||
|
||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||
|
||||
@@ -39,11 +40,18 @@ const GRPCAT = {
|
||||
'mc': 'projectiles',
|
||||
'axmc': 'experimental',
|
||||
'fc': 'projectiles',
|
||||
'rfl': 'experimental',
|
||||
'rfl': 'experimental',
|
||||
'pa': 'projectiles',
|
||||
'rg': 'projectiles',
|
||||
'mr': 'ordnance',
|
||||
'axmr': 'experimental',
|
||||
'rcpl': 'experimental',
|
||||
'dtl': 'experimental',
|
||||
'tbsc': 'experimental',
|
||||
'tbem': 'experimental',
|
||||
'tbrfl': 'experimental',
|
||||
'mahr': 'experimental',
|
||||
'rsl': 'experimental',
|
||||
'tp': 'ordnance',
|
||||
'nl': 'ordnance',
|
||||
'sc': 'scanners',
|
||||
@@ -56,7 +64,25 @@ const GRPCAT = {
|
||||
'ch': 'defence',
|
||||
'po': 'defence',
|
||||
'ec': 'defence',
|
||||
'sfn': 'defence'
|
||||
'sfn': 'defence',
|
||||
// Guardian
|
||||
'gpp': 'guardian',
|
||||
'gpc': 'guardian',
|
||||
'gsrp': 'guardian',
|
||||
'ggc': 'guardian',
|
||||
'gfsb': 'guardian',
|
||||
'gmrp': 'guardian',
|
||||
'gsc': 'guardian',
|
||||
'ghrp': 'guardian',
|
||||
|
||||
// Mining
|
||||
'scl': 'mining',
|
||||
'pwa': 'mining',
|
||||
'sdm': 'mining',
|
||||
|
||||
// Assists
|
||||
'dc': 'flight assists',
|
||||
'sua': 'flight assists',
|
||||
};
|
||||
// Order here is the order in which items will be shown in the modules menu
|
||||
const CATEGORIES = {
|
||||
@@ -71,9 +97,10 @@ const CATEGORIES = {
|
||||
'rf': ['rf'],
|
||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
||||
'structural reinforcement': ['hr', 'mrp'],
|
||||
'dc': ['dc'],
|
||||
'flight assists': ['dc', 'sua'],
|
||||
|
||||
// Hardpoints
|
||||
'lasers': ['pl', 'ul', 'bl', 'ml'],
|
||||
'lasers': ['pl', 'ul', 'bl'],
|
||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
||||
'ordnance': ['mr', 'tp', 'nl'],
|
||||
// Utilities
|
||||
@@ -82,25 +109,29 @@ const CATEGORIES = {
|
||||
'defence': ['ch', 'po', 'ec'],
|
||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
||||
// Experimental
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn']
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
|
||||
|
||||
// Guardian
|
||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
|
||||
|
||||
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Available modules menu
|
||||
*/
|
||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
diffDetails: PropTypes.func,
|
||||
m: PropTypes.object,
|
||||
shipMass: PropTypes.number,
|
||||
warning: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
shipMass: 0
|
||||
ship: PropTypes.object.isRequired,
|
||||
warning: PropTypes.func,
|
||||
firstSlotId: PropTypes.string,
|
||||
lastSlotId: PropTypes.string,
|
||||
activeSlotId: PropTypes.string,
|
||||
slotDiv: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -111,7 +142,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this._hideDiff = this._hideDiff.bind(this);
|
||||
this._showSearch = this._showSearch.bind(this);
|
||||
this.state = this._initState(props, context);
|
||||
this.slotItems = [];// Array to hold <li> refs.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,27 +155,33 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
*/
|
||||
_initState(props, context) {
|
||||
let translate = context.language.translate;
|
||||
let { m, warning, shipMass, onSelect, modules } = props;
|
||||
let { m, warning, onSelect, modules, ship } = props;
|
||||
let list, currentGroup;
|
||||
|
||||
let buildGroup = this._buildGroup.bind(
|
||||
this,
|
||||
ship,
|
||||
translate,
|
||||
m,
|
||||
warning,
|
||||
shipMass - (m && m.mass ? m.mass : 0),
|
||||
(m, event) => {
|
||||
this._hideDiff(event);
|
||||
onSelect(m);
|
||||
}
|
||||
);
|
||||
|
||||
let fuzzy = [];
|
||||
if (modules instanceof Array) {
|
||||
list = buildGroup(modules[0].grp, modules);
|
||||
} else {
|
||||
list = [];
|
||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
||||
if (m) {
|
||||
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
|
||||
let emptyId = 'empty';
|
||||
if (this.firstSlotId == null) this.firstSlotId = emptyId;
|
||||
let keyDown = this._keyDown.bind(this, onSelect);
|
||||
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
|
||||
onKeyDown={keyDown} tabIndex="0"
|
||||
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
|
||||
}
|
||||
|
||||
// Need to regroup the modules by our own categorisation
|
||||
@@ -170,7 +209,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
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>);
|
||||
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>);
|
||||
}
|
||||
@@ -181,54 +221,71 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
categoryHeader = true;
|
||||
}
|
||||
if (m && grp == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
||||
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]));
|
||||
for (const i of modules[grp]) {
|
||||
let mount = '';
|
||||
if (i.mount === 'F') {
|
||||
mount = 'Fixed';
|
||||
} else if (i.mount === 'G') {
|
||||
mount = 'Gimballed';
|
||||
} else if (i.mount === 'T') {
|
||||
mount = 'Turreted';
|
||||
}
|
||||
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
|
||||
fuzzy.push(fuzz);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { list, currentGroup };
|
||||
let trackingFocus = false;
|
||||
return { list, currentGroup, fuzzy, trackingFocus };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate React Components for Module Group
|
||||
* @param {Ship} ship Ship the selection is for
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Objecy} mountedModule Mounted Module
|
||||
* @param {Funciton} warningFunc Warning function
|
||||
* @param {number} mass Mass
|
||||
* @param {Object} mountedModule Mounted Module
|
||||
* @param {Function} warningFunc Warning function
|
||||
* @param {function} onSelect Select/Mount callback
|
||||
* @param {string} grp Group name
|
||||
* @param {Array} modules Available modules
|
||||
* @return {React.Component} Available Module Group contents
|
||||
*/
|
||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
|
||||
let prevClass = null, prevRating = null;
|
||||
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) {
|
||||
let prevClass = null, prevRating = null, prevName;
|
||||
let elems = [];
|
||||
|
||||
const sortedModules = modules.sort(this._moduleOrder);
|
||||
|
||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
|
||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
|
||||
count[cls] = ++count[cls] || 1;
|
||||
return count;
|
||||
}, {});
|
||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
||||
|
||||
let itemsOnThisRow = 0;
|
||||
|
||||
for (let i = 0; i < sortedModules.length; i++) {
|
||||
let m = sortedModules[i];
|
||||
let mount = null;
|
||||
let disabled = false;
|
||||
prevName = m.name;
|
||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
||||
// Shield generators care about maximum hull mass
|
||||
disabled = mass > m.maxmass;
|
||||
} else if (m.maxmass) {
|
||||
// Thrusters care about total mass
|
||||
disabled = mass + m.mass > m.maxmass;
|
||||
disabled = ship.hullMass > m.maxmass;
|
||||
// If the mounted module is experimental as well, we can replace it so
|
||||
// the maximum does not apply
|
||||
} else if (m.experimental && (!mountedModule || !mountedModule.experimental)) {
|
||||
disabled = 4 <= ship.hardpoints.filter(o => o.m && o.m.experimental).length;
|
||||
}
|
||||
let active = mountedModule && mountedModule.id === m.id;
|
||||
let classes = cn(m.name ? 'lc' : 'c', {
|
||||
@@ -238,9 +295,21 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
});
|
||||
let eventHandlers;
|
||||
|
||||
if (disabled || active) {
|
||||
eventHandlers = {};
|
||||
if (disabled) {
|
||||
eventHandlers = {
|
||||
onKeyDown: this._keyDown.bind(this, null),
|
||||
onKeyUp: this._keyUp.bind(this, null)
|
||||
|
||||
};
|
||||
} else {
|
||||
/**
|
||||
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
|
||||
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
|
||||
*/
|
||||
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
|
||||
if (active) this.activeSlotId = sortedModules[i].id;
|
||||
this.lastSlotId = sortedModules[i].id;
|
||||
|
||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
||||
let select = onSelect.bind(null, m);
|
||||
|
||||
@@ -249,33 +318,46 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||
onTouchEnd: this._touchEnd.bind(this, select),
|
||||
onMouseLeave: this._hideDiff,
|
||||
onClick: select
|
||||
onClick: select,
|
||||
onKeyDown: this._keyDown.bind(this, select),
|
||||
onKeyUp: this._keyUp.bind(this, select)
|
||||
};
|
||||
}
|
||||
|
||||
switch(m.mount) {
|
||||
case 'F': mount = <MountFixed className={'lg'} />; break;
|
||||
case 'G': mount = <MountGimballed className={'lg'}/>; break;
|
||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
||||
switch (m.mount) {
|
||||
case 'F':
|
||||
mount = <MountFixed className={'lg'}/>;
|
||||
break;
|
||||
case 'G':
|
||||
mount = <MountGimballed className={'lg'}/>;
|
||||
break;
|
||||
case 'T':
|
||||
mount = <MountTurret className={'lg'}/>;
|
||||
break;
|
||||
}
|
||||
|
||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
||||
elems.push(<br key={'b' + m.grp + i} />);
|
||||
if (m.name && m.name === prevName) {
|
||||
// elems.push(<br key={'b' + m.grp + i} />);
|
||||
itemsOnThisRow = 0;
|
||||
}
|
||||
|
||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
||||
elems.push(<br key={'b' + m.grp + i}/>);
|
||||
itemsOnThisRow = 0;
|
||||
}
|
||||
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
|
||||
elems.push(
|
||||
<li key={m.id} className={classes} {...eventHandlers}>
|
||||
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx}
|
||||
ref={slotItem => this.slotItems[m.id] = slotItem}>
|
||||
{mount}
|
||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
||||
</li>
|
||||
);
|
||||
|
||||
itemsOnThisRow++;
|
||||
prevClass = m.class;
|
||||
prevRating = m.rating;
|
||||
prevName = m.name;
|
||||
}
|
||||
|
||||
return <ul key={'modules' + grp} >{elems}</ul>;
|
||||
return <ul key={'modules' + grp}>{elems}</ul>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,6 +374,40 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tooltip content for the difference between the
|
||||
* mounted module and the hovered modules
|
||||
*/
|
||||
_showSearch() {
|
||||
if (this.props.modules instanceof Array) {
|
||||
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>
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse over diff handler
|
||||
* @param {Function} showDiff diff tooltip callback
|
||||
@@ -326,6 +442,41 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
this._hideDiff();
|
||||
}
|
||||
|
||||
/**
|
||||
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
|
||||
* @param {Function} select Select module callback
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_keyDown(select, event) {
|
||||
let className = event.currentTarget.attributes['class'].value;
|
||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
||||
select();
|
||||
return;
|
||||
}
|
||||
let elemId = event.currentTarget.attributes['data-id'].value;
|
||||
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
|
||||
if (event.shiftKey && elemId == this.firstSlotId) {
|
||||
event.preventDefault();
|
||||
this.slotItems[this.lastSlotId].focus();
|
||||
return;
|
||||
}
|
||||
if (!event.shiftKey && elemId == this.lastSlotId) {
|
||||
event.preventDefault();
|
||||
this.slotItems[this.firstSlotId].focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key Up
|
||||
* @param {Function} select Select module callback
|
||||
* @param {SytheticEvent} event Event
|
||||
*/
|
||||
_keyUp(select, event) {
|
||||
// nothing here yet
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide diff tooltip
|
||||
* @param {SyntheticEvent} event Event
|
||||
@@ -383,6 +534,24 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
if (this.groupElem) { // Scroll to currently selected group
|
||||
this.node.scrollTop = this.groupElem.offsetTop;
|
||||
}
|
||||
/**
|
||||
* Set focus on active or first slot element, if applicable.
|
||||
*/
|
||||
if (this.slotItems[this.activeSlotId]) {
|
||||
this.slotItems[this.activeSlotId].focus();
|
||||
} else if (this.slotItems[this.firstSlotId]) {
|
||||
this.slotItems[this.firstSlotId].focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle focus if the component updates
|
||||
*
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.props.slotDiv) {
|
||||
this.props.slotDiv.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,14 +570,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
render() {
|
||||
return (
|
||||
<div ref={node => this.node = node}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{this._showSearch()}
|
||||
{this.state.list}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
|
||||
* Bar Chart
|
||||
*/
|
||||
export default class BarChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
||||
labels: null,
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
|
||||
/**
|
||||
* Boost displays a boost button that toggles bosot
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
|
||||
* Comparison Table
|
||||
*/
|
||||
export default class ComparisonTable extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
facets: PropTypes.array.isRequired,
|
||||
builds: PropTypes.array.isRequired,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ShoppingIcon } from '../components/SvgIcons';
|
||||
* Cost Section
|
||||
*/
|
||||
export default class CostSection extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
@@ -307,8 +306,8 @@ export default class CostSection extends TranslatedComponent {
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
||||
{translate('module')}
|
||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * shipDiscount)}]`}</u> : null}
|
||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
|
||||
</th>
|
||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
||||
</tr>
|
||||
@@ -361,11 +360,11 @@ export default class CostSection extends TranslatedComponent {
|
||||
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>
|
||||
<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 {
|
||||
|
||||
@@ -52,12 +52,12 @@ export default class Defence extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, sys, opponentWep } = this.props;
|
||||
const { opponent, sys, opponentWep } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { shield, armour, shielddamage, armourdamage } = this.state;
|
||||
|
||||
const pd = ship.standard[4].m;
|
||||
const pd = opponent.standard[4].m;
|
||||
|
||||
const shieldSourcesData = [];
|
||||
const effectiveShieldData = [];
|
||||
@@ -76,6 +76,7 @@ export default class Defence extends TranslatedComponent {
|
||||
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
|
||||
|
||||
if (shield.generator > 0) {
|
||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
@@ -101,19 +102,19 @@ export default class Defence extends TranslatedComponent {
|
||||
|
||||
// Add effective shield from resistances
|
||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
||||
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
|
||||
const explosiveMj = rawMj / (shield.explosive.base) - rawMj;
|
||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
||||
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
|
||||
const kineticMj = rawMj / (shield.kinetic.base) - rawMj;
|
||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
||||
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
|
||||
const thermalMj = rawMj / (shield.thermal.base) - rawMj;
|
||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
||||
|
||||
// Add effective shield from power distributor SYS pips
|
||||
if (shield.absolute.sys != 1) {
|
||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,18 +160,21 @@ export default class Defence extends TranslatedComponent {
|
||||
const effectiveArmourExplosiveTt = [];
|
||||
const effectiveArmourKineticTt = [];
|
||||
const effectiveArmourThermalTt = [];
|
||||
const effectiveArmourCausticTt = [];
|
||||
if (armour.bulkheads > 0) {
|
||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
if (armour.reinforcement > 0) {
|
||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,17 +187,22 @@ export default class Defence extends TranslatedComponent {
|
||||
const armourDamageTakenExplosiveTt = [];
|
||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
||||
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenKineticTt = [];
|
||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
||||
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenThermalTt = [];
|
||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
||||
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenCausticTt = [];
|
||||
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
|
||||
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
|
||||
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
|
||||
|
||||
const effectiveArmourData = [];
|
||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
||||
@@ -204,36 +213,39 @@ export default class Defence extends TranslatedComponent {
|
||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
|
||||
const effectiveCausticArmour = armour.total / armour.caustic.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
|
||||
|
||||
const armourDamageTakenData = [];
|
||||
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
|
||||
|
||||
return (
|
||||
<span id='defence'>
|
||||
{shield.total ? <span>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
</span> : null }
|
||||
|
||||
<div className='group quarter'>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import 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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import 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';
|
||||
|
||||
/**
|
||||
@@ -57,7 +52,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
*/
|
||||
_calcMaxRange(ship, fuel, mass) {
|
||||
// Obtain the maximum range
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +64,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
|
||||
|
||||
|
||||
// Calculate bounds for our line chart - use thruster info for X
|
||||
const thrusters = ship.standard[1].m;
|
||||
const fsd = ship.standard[2].m;
|
||||
@@ -77,7 +72,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minRange = 0;
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship);
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,21 @@ 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 {
|
||||
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
|
||||
*/
|
||||
@@ -27,7 +36,7 @@ export default class HardpointSlot extends Slot {
|
||||
* @return {string} Label
|
||||
*/
|
||||
_getMaxClassLabel(translate) {
|
||||
return translate(['U','S','M','L','H'][this.props.maxClass]);
|
||||
return translate(['U', 'S', 'M', 'L', 'H'][this.props.maxClass]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,42 +75,77 @@ export default class HardpointSlot extends Slot {
|
||||
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 }
|
||||
{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 }
|
||||
{m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')}
|
||||
onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} {m.getClip() ?
|
||||
<span>({formats.round1(m.getSDps())})</span> : null}</div> : null}
|
||||
{m.getDamage() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getDamage() ? 'shotdmg' : 'shotdmg')}
|
||||
onMouseOut={tooltip.bind(null, null)}>{translate('shotdmg')}: {formats.round1(m.getDamage())}</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.getEps() * m.getSustainedFactor())}{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.getHps() * m.getSustainedFactor())})</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('wep_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}
|
||||
{m.getScanAngle() ? <div className={'l'}>{translate('scan angle')}: {formats.f2(m.getScanAngle())}°</div> : null}
|
||||
{m.getScanRange() ? <div className={'l'}>{translate('scan range')}: {formats.int(m.getScanRange())}{u.m}</div> : null}
|
||||
{m.getMaxAngle() ? <div className={'l'}>{translate('max angle')}: {formats.f2(m.getMaxAngle())}°</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' tabIndex="0" ref={modButton => this.modButton = modButton}>
|
||||
<button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation}
|
||||
onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}>
|
||||
<ListModifications/></button>
|
||||
</div> : null}
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className={'empty'}>{translate('empty')}</div>;
|
||||
return <div className={'empty'}>{translate('empty')}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import cn from 'classnames';
|
||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
@@ -17,14 +16,25 @@ export default class HardpointSlotSection extends SlotSection {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'hardpoints', 'hardpoints');
|
||||
|
||||
this._empty = this._empty.bind(this);
|
||||
this.selectedRefId = null;
|
||||
this.firstRefId = 'emptyall';
|
||||
this.lastRefId = 'nl-F';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle focus when component updates
|
||||
* @param {Object} prevProps React Component properties
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all slots
|
||||
*/
|
||||
_empty() {
|
||||
this.selectedRefId = 'emptyall';
|
||||
this.props.ship.emptyWeapons();
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
@@ -37,6 +47,7 @@ export default class HardpointSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fill(group, mount, event) {
|
||||
this.selectedRefId = group + '-' + mount;
|
||||
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
@@ -95,52 +106,61 @@ export default class HardpointSlotSection extends SlotSection {
|
||||
|
||||
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('pl')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('ul')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('bl')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('mc')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('c')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('fc')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
|
||||
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-G'] = smRef}><MountGimballed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('pa')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('rg')}</div>
|
||||
<ul>
|
||||
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'rg', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rg-F'] = smRef}>{translate('rg')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('nl')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('rfl')}</div>
|
||||
<ul>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>
|
||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-T'] = smRef}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Persist from '../stores/Persist';
|
||||
import { toDetailedExport } from '../shipyard/Serializer';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import ModalDeleteAll from './ModalDeleteAll';
|
||||
import ModalExport from './ModalExport';
|
||||
import ModalHelp from './ModalHelp';
|
||||
import ModalImport from './ModalImport';
|
||||
import Slider from './Slider';
|
||||
import Announcement from './Announcement';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
const SIZE_MIN = 0.65;
|
||||
@@ -74,8 +76,11 @@ export default class Header extends TranslatedComponent {
|
||||
this._openShips = this._openMenu.bind(this, 's');
|
||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||
this._openComp = this._openMenu.bind(this, 'comp');
|
||||
this._openAnnounce = this._openMenu.bind(this, 'announce');
|
||||
this._getAnnouncementsMenu = this._getAnnouncementsMenu.bind(this);
|
||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||
this._showHelp = this._showHelp.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
this.languageOptions = [];
|
||||
this.insuranceOptions = [];
|
||||
this.state = {
|
||||
@@ -306,7 +311,7 @@ export default class Header extends TranslatedComponent {
|
||||
_getShipsMenu() {
|
||||
let shipList = [];
|
||||
|
||||
for (let s in Ships) {
|
||||
for (let s of this.shipOrder) {
|
||||
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
||||
}
|
||||
|
||||
@@ -372,6 +377,32 @@ export default class Header extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the announcement menu
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getAnnouncementsMenu() {
|
||||
let announcements;
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (this.props.announcements) {
|
||||
announcements = [];
|
||||
for (let announce of this.props.announcements) {
|
||||
if (announce.expiry < Date.now()) {
|
||||
continue;
|
||||
}
|
||||
announcements.push(<Announcement text={announce.text} />);
|
||||
announcements.push(<hr/>);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
||||
{announcements}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the settings menu
|
||||
* @return {React.Component} Menu
|
||||
@@ -494,6 +525,15 @@ export default class Header extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
const reg = await navigator.serviceWorker.getRegistration();
|
||||
if (!reg || !reg.waiting) {
|
||||
return window.location.reload();
|
||||
}
|
||||
reg.waiting.postMessage('skipWaiting');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the header
|
||||
* @return {React.Component} Header
|
||||
@@ -504,7 +544,10 @@ export default class Header extends TranslatedComponent {
|
||||
let hasBuilds = Persist.hasBuilds();
|
||||
return (
|
||||
<header>
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={this.update}>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
|
||||
{'View Release Changes'}
|
||||
</a> : null}
|
||||
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
|
||||
|
||||
<div className='l menu'>
|
||||
@@ -528,6 +571,23 @@ export default class Header extends TranslatedComponent {
|
||||
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
||||
</div>
|
||||
|
||||
<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'>
|
||||
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
|
||||
<div className={cn('menu-header')}>
|
||||
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
<div className='r menu'>
|
||||
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
|
||||
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>
|
||||
|
||||
@@ -33,6 +33,9 @@ export default class InternalSlot extends Slot {
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
modTT += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
@@ -60,6 +63,9 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
|
||||
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
|
||||
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
|
||||
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
||||
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
|
||||
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
@@ -67,21 +73,22 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
||||
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
||||
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
{ 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 }
|
||||
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import InternalSlot from './InternalSlot';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
@@ -18,7 +17,6 @@ export default class InternalSlotSection extends SlotSection {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'internal', 'optional internal');
|
||||
|
||||
this._empty = this._empty.bind(this);
|
||||
this._fillWithCargo = this._fillWithCargo.bind(this);
|
||||
this._fillWithCells = this._fillWithCells.bind(this);
|
||||
@@ -29,12 +27,24 @@ export default class InternalSlotSection extends SlotSection {
|
||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
|
||||
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
|
||||
this.selectedRefId = null;
|
||||
this.firstRefId = 'emptyall';
|
||||
this.lastRefId = this.sectionRefArr['pcq'] ? 'pcq' : 'pcm';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle focus when component updates
|
||||
* @param {Object} prevProps React Component properties
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all slots
|
||||
*/
|
||||
_empty() {
|
||||
this.selectedRefId = 'emptyall';
|
||||
this.props.ship.emptyInternal();
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
@@ -45,6 +55,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithCargo(event) {
|
||||
this.selectedRefId = 'cargo';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -61,6 +72,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFuelTanks(event) {
|
||||
this.selectedRefId = 'ft';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -77,6 +89,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithLuxuryCabins(event) {
|
||||
this.selectedRefId = 'pcq';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -93,6 +106,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFirstClassCabins(event) {
|
||||
this.selectedRefId = 'pcm';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -109,6 +123,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithBusinessClassCabins(event) {
|
||||
this.selectedRefId = 'pci';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -125,6 +140,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithEconomyClassCabins(event) {
|
||||
this.selectedRefId = 'pce';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -141,6 +157,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithCells(event) {
|
||||
this.selectedRefId = 'scb';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
let chargeCap = 0; // Capacity of single activation
|
||||
@@ -160,6 +177,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithArmor(event) {
|
||||
this.selectedRefId = 'hr';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -176,6 +194,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithModuleReinforcementPackages(event) {
|
||||
this.selectedRefId = 'mrp';
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
@@ -240,16 +259,16 @@ export default class InternalSlotSection extends SlotSection {
|
||||
_getSectionMenu(translate, ship) {
|
||||
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
||||
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
||||
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
|
||||
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
||||
<li className='lc' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
||||
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li>
|
||||
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
|
||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
|
||||
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
|
||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
@@ -61,7 +57,7 @@ export default class JumpRange extends TranslatedComponent {
|
||||
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
||||
|
||||
// Obtain the jump range
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,283 +1,281 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
|
||||
/**
|
||||
* Line Chart
|
||||
*/
|
||||
export default class LineChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
code: '',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
points: 20,
|
||||
colors: ['#ff8c0d'],
|
||||
aspect: 0.5
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
func: PropTypes.func.isRequired,
|
||||
xLabel: PropTypes.string.isRequired,
|
||||
xMin: PropTypes.number,
|
||||
xMax: PropTypes.number.isRequired,
|
||||
xUnit: PropTypes.string.isRequired,
|
||||
xMark: PropTypes.number,
|
||||
yLabel: PropTypes.string.isRequired,
|
||||
yMin: PropTypes.number,
|
||||
yMax: PropTypes.number.isRequired,
|
||||
yUnit: PropTypes.string,
|
||||
series: PropTypes.array,
|
||||
colors: PropTypes.array,
|
||||
points: PropTypes.number,
|
||||
aspect: PropTypes.number,
|
||||
code: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateSeries = this._updateSeries.bind(this);
|
||||
this._tooltip = this._tooltip.bind(this);
|
||||
this._showTip = this._showTip.bind(this);
|
||||
this._hideTip = this._hideTip.bind(this);
|
||||
this._moveTip = this._moveTip.bind(this);
|
||||
|
||||
const series = props.series;
|
||||
|
||||
let xScale = d3.scaleLinear();
|
||||
let yScale = d3.scaleLinear();
|
||||
let xAxisScale = d3.scaleLinear();
|
||||
|
||||
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
|
||||
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
|
||||
|
||||
this.state = {
|
||||
xScale,
|
||||
xAxisScale,
|
||||
yScale,
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||
dimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tooltip content
|
||||
* @param {number} xPos x coordinate
|
||||
*/
|
||||
_tooltip(xPos) {
|
||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||
let { xScale, yScale } = this.state;
|
||||
let { width } = this.state.dimensions;
|
||||
let { formats, translate } = this.context.language;
|
||||
let x0 = xScale.invert(xPos),
|
||||
y0 = func(x0),
|
||||
tips = this.tipContainer,
|
||||
yTotal = 0,
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
|
||||
xPos = xScale(x0); // Clamp xPos
|
||||
|
||||
tips.selectAll('text.text-tip.y').text(function(d, i) {
|
||||
let yVal = series ? y0[series[i]] : y0;
|
||||
yTotal += yVal;
|
||||
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
|
||||
|
||||
tips.selectAll('text').each(function() {
|
||||
if (this.getBBox().width > tipWidth) {
|
||||
tipWidth = Math.ceil(this.getBBox().width);
|
||||
}
|
||||
});
|
||||
|
||||
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
|
||||
|
||||
tipWidth += 8;
|
||||
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
|
||||
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
|
||||
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
|
||||
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
|
||||
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions based on properties and scale
|
||||
* @param {Object} props React Component properties
|
||||
* @param {number} scale size ratio / scale
|
||||
* @returns {Object} calculated dimensions
|
||||
*/
|
||||
_updateDimensions(props, scale) {
|
||||
const { xMax, xMin, yMin, yMax } = props;
|
||||
const { width, height } = this.state.dimensions;
|
||||
const innerWidth = width - MARGIN.left - MARGIN.right;
|
||||
const outerHeight = Math.round(width * props.aspect);
|
||||
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||
|
||||
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
|
||||
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
|
||||
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
|
||||
return { innerWidth, outerHeight, innerHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', null);
|
||||
this.markersContainer.style('display', null);
|
||||
this._moveTip(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and update tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_moveTip(e) {
|
||||
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_hideTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', 'none');
|
||||
this.markersContainer.style('display', 'none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update series generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} state React Component state
|
||||
*/
|
||||
_updateSeries(props, state) {
|
||||
let { func, xMin, xMax, series, points } = props;
|
||||
let delta = (xMax - xMin) / points;
|
||||
let seriesData = new Array(points);
|
||||
|
||||
if (delta) {
|
||||
seriesData = new Array(points);
|
||||
for (let i = 0, x = xMin; i < points; i++) {
|
||||
seriesData[i] = [x, func(x)];
|
||||
x += delta;
|
||||
}
|
||||
seriesData[points - 1] = [xMax, func(xMax)];
|
||||
} else {
|
||||
let yVal = func(xMin);
|
||||
seriesData = [[0, yVal], [1, yVal]];
|
||||
}
|
||||
|
||||
const markerElems = [];
|
||||
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
|
||||
const seriesLines = [];
|
||||
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
|
||||
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
|
||||
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
|
||||
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
|
||||
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||
}
|
||||
|
||||
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
|
||||
|
||||
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions and series data based on props and context.
|
||||
*/
|
||||
componentWillMount() {
|
||||
this._updateSeries(this.props, this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.code != nextProps.code) {
|
||||
this._updateSeries(nextProps, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the chart
|
||||
* @return {React.Component} Chart SVG
|
||||
*/
|
||||
render() {
|
||||
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
|
||||
const { width, height } = this.state.dimensions;
|
||||
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
|
||||
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||
const line = this.line;
|
||||
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
|
||||
|
||||
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
|
||||
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
||||
|
||||
return (
|
||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
||||
<div width={width} height={height}>
|
||||
<svg style={{ width: '100%', height: outerHeight }}>
|
||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||
<g>{xmark}</g>
|
||||
<g>{lines}</g>
|
||||
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
|
||||
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{xLabel}</tspan>
|
||||
<tspan className='metric'> ({xUnit})</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
|
||||
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{yLabel}</tspan>
|
||||
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
|
||||
</text>
|
||||
</g>
|
||||
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
<rect className='tooltip' height={tipHeight + 'em'}></rect>
|
||||
{detailElems}
|
||||
</g>
|
||||
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
{markerElems}
|
||||
</g>
|
||||
<rect
|
||||
fillOpacity='0'
|
||||
height={innerHeight}
|
||||
width={innerWidth + 1}
|
||||
onMouseEnter={this._showTip}
|
||||
onTouchStart={this._showTip}
|
||||
onMouseLeave={this._hideTip}
|
||||
onTouchEnd={this._hideTip}
|
||||
onMouseMove={this._moveTip}
|
||||
onTouchMove={this._moveTip}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
|
||||
/**
|
||||
* Line Chart
|
||||
*/
|
||||
export default class LineChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
code: '',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
points: 20,
|
||||
colors: ['#ff8c0d'],
|
||||
aspect: 0.5
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
func: PropTypes.func.isRequired,
|
||||
xLabel: PropTypes.string.isRequired,
|
||||
xMin: PropTypes.number,
|
||||
xMax: PropTypes.number.isRequired,
|
||||
xUnit: PropTypes.string.isRequired,
|
||||
xMark: PropTypes.number,
|
||||
yLabel: PropTypes.string.isRequired,
|
||||
yMin: PropTypes.number,
|
||||
yMax: PropTypes.number.isRequired,
|
||||
yUnit: PropTypes.string,
|
||||
series: PropTypes.array,
|
||||
colors: PropTypes.array,
|
||||
points: PropTypes.number,
|
||||
aspect: PropTypes.number,
|
||||
code: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateSeries = this._updateSeries.bind(this);
|
||||
this._tooltip = this._tooltip.bind(this);
|
||||
this._showTip = this._showTip.bind(this);
|
||||
this._hideTip = this._hideTip.bind(this);
|
||||
this._moveTip = this._moveTip.bind(this);
|
||||
|
||||
const series = props.series;
|
||||
|
||||
let xScale = d3.scaleLinear();
|
||||
let yScale = d3.scaleLinear();
|
||||
let xAxisScale = d3.scaleLinear();
|
||||
|
||||
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
|
||||
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
|
||||
|
||||
this.state = {
|
||||
xScale,
|
||||
xAxisScale,
|
||||
yScale,
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tooltip content
|
||||
* @param {number} xPos x coordinate
|
||||
* @param {number} width current container width
|
||||
*/
|
||||
_tooltip(xPos, width) {
|
||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||
let { xScale, yScale } = this.state;
|
||||
let { formats, translate } = this.context.language;
|
||||
let x0 = xScale.invert(xPos),
|
||||
y0 = func(x0),
|
||||
tips = this.tipContainer,
|
||||
yTotal = 0,
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
|
||||
xPos = xScale(x0); // Clamp xPos
|
||||
|
||||
tips.selectAll('text.text-tip.y').text(function(d, i) {
|
||||
let yVal = series ? y0[series[i]] : y0;
|
||||
yTotal += yVal;
|
||||
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
|
||||
|
||||
tips.selectAll('text').each(function() {
|
||||
if (this.getBBox().width > tipWidth) {
|
||||
tipWidth = Math.ceil(this.getBBox().width);
|
||||
}
|
||||
});
|
||||
|
||||
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
|
||||
|
||||
tipWidth += 8;
|
||||
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
|
||||
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
|
||||
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
|
||||
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
|
||||
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions based on properties and scale
|
||||
* @param {Object} props React Component properties
|
||||
* @param {number} scale size ratio / scale
|
||||
* @param {number} width current width of the container
|
||||
* @returns {Object} calculated dimensions
|
||||
*/
|
||||
_updateDimensions(props, scale, width) {
|
||||
const { xMax, xMin, yMin, yMax } = props;
|
||||
const innerWidth = width - MARGIN.left - MARGIN.right;
|
||||
const outerHeight = Math.round(width * props.aspect);
|
||||
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||
|
||||
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
|
||||
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
|
||||
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
|
||||
return { innerWidth, outerHeight, innerHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', null);
|
||||
this.markersContainer.style('display', null);
|
||||
this._moveTip(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and update tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
* @param {number} width current container width
|
||||
*/
|
||||
_moveTip(e, width) {
|
||||
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_hideTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', 'none');
|
||||
this.markersContainer.style('display', 'none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update series generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} state React Component state
|
||||
*/
|
||||
_updateSeries(props, state) {
|
||||
let { func, xMin, xMax, series, points } = props;
|
||||
let delta = (xMax - xMin) / points;
|
||||
let seriesData = new Array(points);
|
||||
|
||||
if (delta) {
|
||||
seriesData = new Array(points);
|
||||
for (let i = 0, x = xMin; i < points; i++) {
|
||||
seriesData[i] = [x, func(x)];
|
||||
x += delta;
|
||||
}
|
||||
seriesData[points - 1] = [xMax, func(xMax)];
|
||||
} else {
|
||||
let yVal = func(xMin);
|
||||
seriesData = [[0, yVal], [1, yVal]];
|
||||
}
|
||||
|
||||
const markerElems = [];
|
||||
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
|
||||
const seriesLines = [];
|
||||
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
|
||||
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
|
||||
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
|
||||
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
|
||||
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||
}
|
||||
|
||||
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
|
||||
|
||||
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions and series data based on props and context.
|
||||
*/
|
||||
componentWillMount() {
|
||||
this._updateSeries(this.props, this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.code != nextProps.code) {
|
||||
this._updateSeries(nextProps, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the chart
|
||||
* @return {React.Component} Chart SVG
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ContainerDimensions>
|
||||
{ ({ width, height }) => {
|
||||
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height);
|
||||
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
|
||||
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
|
||||
|
||||
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
|
||||
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
||||
return (
|
||||
<div width={width} height={height}>
|
||||
<svg style={{ width: '100%', height: outerHeight }}>
|
||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||
<g>{xmark}</g>
|
||||
<g>{lines}</g>
|
||||
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
|
||||
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{xLabel}</tspan>
|
||||
<tspan className='metric'> ({xUnit})</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
|
||||
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{yLabel}</tspan>
|
||||
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
|
||||
</text>
|
||||
</g>
|
||||
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
<rect className='tooltip' height={tipHeight + 'em'}></rect>
|
||||
{detailElems}
|
||||
</g>
|
||||
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
{markerElems}
|
||||
</g>
|
||||
<rect
|
||||
fillOpacity='0'
|
||||
height={innerHeight}
|
||||
width={innerWidth + 1}
|
||||
onMouseEnter={this._showTip}
|
||||
onTouchStart={this._showTip}
|
||||
onMouseLeave={this._hideTip}
|
||||
onTouchEnd={this._hideTip}
|
||||
onMouseMove={e => this._moveTip(e, width)}
|
||||
onTouchMove={e => this._moveTip(e, width)}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
|
||||
* Delete All saved data modal
|
||||
*/
|
||||
export default class ModalDeleteAll extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Delete everything and hide the modal
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,10 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
||||
import { Download } from './SvgIcons';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||
import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils';
|
||||
import { shipFromLoadoutJSON } from '../utils/JournalUtils';
|
||||
|
||||
const zlib = require('pako');
|
||||
|
||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||
@@ -86,6 +89,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
|
||||
|
||||
static propTypes = {
|
||||
importString: PropTypes.string, // Optional: Default data for import modal
|
||||
builds: PropTypes.object, // Optional: Import object
|
||||
};
|
||||
|
||||
@@ -99,11 +103,12 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this.state = {
|
||||
builds: props.builds,
|
||||
canEdit: !props.builds,
|
||||
loadoutEvent: null,
|
||||
comparisons: null,
|
||||
shipDiscount: null,
|
||||
moduleDiscount: null,
|
||||
errorMsg: null,
|
||||
importString: null,
|
||||
importString: props.importString || null,
|
||||
importValid: false,
|
||||
insurance: null
|
||||
};
|
||||
@@ -111,12 +116,28 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this._process = this._process.bind(this);
|
||||
this._import = this._import.bind(this);
|
||||
this._importBackup = this._importBackup.bind(this);
|
||||
this._importLoadout = this._importLoadout.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 Loadout event from Elite: Dangerous journal files
|
||||
* @param {Object} data Loadout event
|
||||
* @throws {string} If import fails
|
||||
*/
|
||||
_importLoadout(data) {
|
||||
if (data && data.Ship && data.Modules) {
|
||||
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
|
||||
let compressed = btoa(deflated);
|
||||
this.setState({loadoutEvent: compressed});
|
||||
} else {
|
||||
throw 'Loadout event must contain Ship and Modules';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a Coriolis backup
|
||||
* @param {Object} importData Backup Data
|
||||
@@ -126,7 +147,11 @@ export default class ModalImport extends TranslatedComponent {
|
||||
if (importData.builds && typeof importData.builds == 'object') {
|
||||
for (let shipId in importData.builds) {
|
||||
for (let buildName in importData.builds[shipId]) {
|
||||
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
|
||||
try {
|
||||
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
|
||||
} catch (err) {
|
||||
delete importData.builds[shipId][buildName];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({ builds: importData.builds });
|
||||
@@ -155,7 +180,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
}
|
||||
// Check for module discount
|
||||
if (!isNaN(importData.moduleDiscount)) {
|
||||
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
|
||||
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
|
||||
}
|
||||
|
||||
if (typeof importData.insurance == 'string') {
|
||||
@@ -191,8 +216,8 @@ export default class ModalImport extends TranslatedComponent {
|
||||
* @throws {string} if parse/import fails
|
||||
*/
|
||||
_importCompanionApiBuild(build) {
|
||||
const shipModel = CompanionApiUtils.shipModelFromJson(build);
|
||||
const ship = CompanionApiUtils.shipFromJson(build);
|
||||
const shipModel = shipModelFromJson(build);
|
||||
const ship = shipFromJson(build);
|
||||
|
||||
let builds = {};
|
||||
builds[shipModel] = {};
|
||||
@@ -298,6 +323,30 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this.setState({ builds, singleBuild: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import SLEF formatted builds. Sets state to a map of the builds on success
|
||||
* and flags if there was only a single build.
|
||||
*
|
||||
* @param {string} importData - Array of the list of builds.
|
||||
* @throws {string} If parse / import fails
|
||||
*/
|
||||
_importSlefBuilds(importData) {
|
||||
const builds = importData.reduce((memo, { data }) => {
|
||||
const shipModel = shipModelFromJson(data);
|
||||
const ship = shipFromLoadoutJSON(data);
|
||||
const shipTemplate = Ships[shipModel];
|
||||
const shipName = data.ShipName || shipTemplate.properties.name;
|
||||
|
||||
const key = `Imported ${shipName}`;
|
||||
memo[shipModel] = {};
|
||||
memo[shipModel][key] = ship.toString();
|
||||
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the import string / text box contents
|
||||
* @param {SyntheticEvent} event Event
|
||||
@@ -332,8 +381,10 @@ export default class ModalImport extends TranslatedComponent {
|
||||
throw 'Must be an object or array!';
|
||||
}
|
||||
|
||||
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData); // Single sihp definition
|
||||
if (importData?.[0]?.header?.appName) { // has SLEF envelope?
|
||||
this._importSlefBuilds(importData);
|
||||
} else if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData); // Single ship definition
|
||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
||||
} else if (importData instanceof Array) { // Must be detailed export json
|
||||
@@ -341,12 +392,14 @@ export default class ModalImport extends TranslatedComponent {
|
||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||
this.setState({ singleBuild: true });
|
||||
} else if (importData.Modules != null && importData.Modules[0] != null) {
|
||||
this._importLoadout(importData);
|
||||
} else { // Using Backup JSON
|
||||
this._importBackup(importData);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e.stack);
|
||||
console.log(e);
|
||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
||||
return;
|
||||
}
|
||||
@@ -360,6 +413,10 @@ export default class ModalImport extends TranslatedComponent {
|
||||
_process() {
|
||||
let builds = null, comparisons = null;
|
||||
|
||||
if (this.state.loadoutEvent) {
|
||||
return Router.go(`/import?data=${this.state.loadoutEvent}`);
|
||||
}
|
||||
|
||||
// If only importing a single build go straight to the outfitting page
|
||||
if (this.state.singleBuild) {
|
||||
builds = this.state.builds;
|
||||
@@ -476,7 +533,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
if (!state.processed) {
|
||||
importStage = (
|
||||
<div>
|
||||
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
||||
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
||||
</div>
|
||||
@@ -513,7 +570,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
{comparisonRows}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if(this.state.canEdit) {
|
||||
|
||||
@@ -50,6 +50,7 @@ export default class ModalPermalink extends TranslatedComponent {
|
||||
<h3 >{translate('shortened')}</h3>
|
||||
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||
<br/><br/>
|
||||
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
263
src/app/components/ModalShoppingList.jsx
Normal file
263
src/app/components/ModalShoppingList.jsx
Normal file
@@ -0,0 +1,263 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import request from 'superagent';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
/**
|
||||
* Permalink modal
|
||||
*/
|
||||
export default class ModalShoppingList extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
matsList: '',
|
||||
mats: {},
|
||||
failed: false,
|
||||
cmdrName: Persist.getCmdr().selected,
|
||||
cmdrs: Persist.getCmdr().cmdrs,
|
||||
matsPerGrade: Persist.getRolls(),
|
||||
blueprints: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* React component did mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.renderMats();
|
||||
if (this.checkBrowserIsCompatible()) {
|
||||
this.getCommanders();
|
||||
this.registerBPs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all blueprints needed to make a build.
|
||||
*/
|
||||
registerBPs() {
|
||||
const ship = this.props.ship;
|
||||
let blueprints = [];
|
||||
for (const module of ship.costList) {
|
||||
if (module.type === 'SHIP') {
|
||||
continue;
|
||||
}
|
||||
if (module.m && module.m.blueprint) {
|
||||
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
||||
continue;
|
||||
}
|
||||
if (module.m.blueprint.special) {
|
||||
console.log(module.m.blueprint.special);
|
||||
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
|
||||
}
|
||||
for (const g in module.m.blueprint.grades) {
|
||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
||||
continue;
|
||||
}
|
||||
if (g > module.m.blueprint.grade) {
|
||||
continue;
|
||||
}
|
||||
blueprints.push({ uuid: module.m.blueprint.grades[g].uuid, number: this.state.matsPerGrade[g] });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({ blueprints });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check browser isn't firefox.
|
||||
* @return {boolean} true if compatible, false if not.
|
||||
*/
|
||||
checkBrowserIsCompatible() {
|
||||
// Firefox 1.0+
|
||||
return typeof InstallTrigger === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of commanders from EDEngineer.
|
||||
*/
|
||||
getCommanders() {
|
||||
request
|
||||
.get('http://localhost:44405/commanders')
|
||||
.end((err, res) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return this.setState({ failed: true });
|
||||
}
|
||||
const cmdrs = JSON.parse(res.text);
|
||||
if (!this.state.cmdrName) {
|
||||
this.setState({ cmdrName: cmdrs[0] });
|
||||
}
|
||||
this.setState({ cmdrs }, () => {
|
||||
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all blueprints to ED Engineer
|
||||
* @param {Event} event React event
|
||||
*/
|
||||
sendToEDEng(event) {
|
||||
event.preventDefault();
|
||||
let translate = this.context.language.translate;
|
||||
const target = event.target;
|
||||
target.disabled = this.state.blueprints.length > 0;
|
||||
if (this.state.blueprints.length === 0) {
|
||||
target.innerText = translate('No modded components.');
|
||||
target.disabled = true;
|
||||
setTimeout(() => {
|
||||
target.innerText = translate('Send to EDEngineer');
|
||||
target.disabled = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
target.innerText = translate('Sending...');
|
||||
}
|
||||
let countSent = 0;
|
||||
let countTotal = this.state.blueprints.length;
|
||||
|
||||
for (const i of this.state.blueprints) {
|
||||
request
|
||||
.patch(`http://localhost:44405/${this.state.cmdrName}/shopping-list`)
|
||||
.field('uuid', i.uuid)
|
||||
.field('size', i.number)
|
||||
.end(err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
if (err.message !== 'Bad Request') {
|
||||
this.setState({ failed: true });
|
||||
}
|
||||
}
|
||||
countSent++;
|
||||
if (countSent === countTotal) {
|
||||
target.disabled = false;
|
||||
target.innerText = translate('Send to EDEngineer');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert mats object to string
|
||||
*/
|
||||
renderMats() {
|
||||
const ship = this.props.ship;
|
||||
let mats = {};
|
||||
for (const module of ship.costList) {
|
||||
if (module.type === 'SHIP') {
|
||||
continue;
|
||||
}
|
||||
if (module.m && module.m.blueprint) {
|
||||
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
||||
continue;
|
||||
}
|
||||
for (const g in module.m.blueprint.grades) {
|
||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
||||
continue;
|
||||
}
|
||||
if (g > module.m.blueprint.grade) {
|
||||
continue;
|
||||
}
|
||||
for (const i in module.m.blueprint.grades[g].components) {
|
||||
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
if (mats[i]) {
|
||||
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
|
||||
} else {
|
||||
mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let matsString = '';
|
||||
for (const i in mats) {
|
||||
if (!mats.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
if (mats[i] === 0) {
|
||||
delete mats[i];
|
||||
continue;
|
||||
}
|
||||
matsString += `${i}: ${mats[i]}\n`;
|
||||
}
|
||||
this.setState({ matsList: matsString, mats });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changing roll amounts
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
changeHandler(e) {
|
||||
let grade = e.target.id;
|
||||
let newState = this.state.matsPerGrade;
|
||||
newState[grade] = parseInt(e.target.value);
|
||||
this.setState({ matsPerGrade: newState });
|
||||
Persist.setRolls(newState);
|
||||
this.renderMats();
|
||||
this.registerBPs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changing cmdr name
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
cmdrChangeHandler(e) {
|
||||
let cmdrName = e.target.value;
|
||||
this.setState({ cmdrName }, () => {
|
||||
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs: this.state.cmdrs });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modal
|
||||
* @return {React.Component} Modal Content
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
this.changeHandler = this.changeHandler.bind(this);
|
||||
const compatible = this.checkBrowserIsCompatible();
|
||||
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
|
||||
this.sendToEDEng = this.sendToEDEng.bind(this);
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
||||
<label>{translate('Grade 1 rolls ')}</label>
|
||||
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
||||
<br/>
|
||||
<label>{translate('Grade 2 rolls ')}</label>
|
||||
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
||||
<br/>
|
||||
<label>{translate('Grade 3 rolls ')}</label>
|
||||
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
||||
<br/>
|
||||
<label>{translate('Grade 4 rolls ')}</label>
|
||||
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
||||
<br/>
|
||||
<label>{translate('Grade 5 rolls ')}</label>
|
||||
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
||||
<div>
|
||||
<textarea className='cb json' readOnly value={this.state.matsList} />
|
||||
</div>
|
||||
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
|
||||
<br/>
|
||||
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
||||
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
|
||||
</select>
|
||||
<br/>
|
||||
<p hidden={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAIL_EDENGINEER')}</p>
|
||||
<p hidden={compatible} id={'browserbad'} className={'l'}>{translate('PHRASE_FIREFOX_EDENGINEER')}</p>
|
||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send to EDEngineer')}</button>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,22 @@ import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import NumberEditor from 'react-number-editor';
|
||||
import { isChangeValueBeneficial } from '../utils/BlueprintFunctions';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class Modification extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onKeyDown: PropTypes.func.isRequired,
|
||||
modItems: PropTypes.array.isRequired,
|
||||
handleModChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -35,31 +39,37 @@ export default class Modification extends TranslatedComponent {
|
||||
* in a value by hand
|
||||
*/
|
||||
_updateValue(value) {
|
||||
const name = this.props.name;
|
||||
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -99.99%
|
||||
if (scaledValue > 100000) {
|
||||
scaledValue = 100000;
|
||||
value = 1000;
|
||||
}
|
||||
if (scaledValue < -9999) {
|
||||
scaledValue = -9999;
|
||||
value = -99.99;
|
||||
}
|
||||
|
||||
let m = this.props.m;
|
||||
let ship = this.props.ship;
|
||||
ship.setModification(m, name, scaledValue, true);
|
||||
|
||||
this.setState({ value });
|
||||
let reCast = String(Number(value));
|
||||
if (reCast.endsWith(value) || reCast.startsWith(value)) {
|
||||
let { m, name, ship } = this.props;
|
||||
value = Math.max(Math.min(value, 50000), -50000);
|
||||
ship.setModification(m, name, value, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a key is pressed down with focus on the number editor.
|
||||
* @param {SyntheticEvent} event Key down event
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
this._updateFinished();
|
||||
}
|
||||
this.props.onKeyDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
||||
*
|
||||
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
|
||||
* with each onBlur event, even when no change has actually been made to the field.
|
||||
*/
|
||||
_updateFinished() {
|
||||
this.props.onChange();
|
||||
if (this.props.value != this.state.value) {
|
||||
this.props.handleModChange(true);
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,28 +77,56 @@ export default class Modification extends TranslatedComponent {
|
||||
* @return {React.Component} modification
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let { m, name } = this.props;
|
||||
let modValue = m.getChange(name);
|
||||
let isOverwrite = Modifications.modifications[name].method === 'overwrite';
|
||||
|
||||
if (name === 'damagedist') {
|
||||
// We don't show damage distribution
|
||||
return null;
|
||||
}
|
||||
|
||||
let symbol;
|
||||
if (name === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (name !== 'burst' && name != 'burstrof') {
|
||||
symbol = '%';
|
||||
}
|
||||
if (symbol) {
|
||||
symbol = ' (' + symbol + ')';
|
||||
}
|
||||
let inputClassNames = {
|
||||
'cb': true,
|
||||
'greyed-out': !this.props.highlight
|
||||
};
|
||||
|
||||
return (
|
||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
|
||||
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
<div onBlur={this._updateFinished.bind(this)} key={name}
|
||||
className={cn('cb', 'modification-container')}
|
||||
ref={ modItem => this.props.modItems[name] = modItem }>
|
||||
<span className={'cb'}>{translate(name, m.grp)}</span>
|
||||
<span className={'header-adjuster'}></span>
|
||||
<table style={{ width: '100%' }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={'input-container'}>
|
||||
<span>
|
||||
{this.props.editable ?
|
||||
<NumberEditor className={cn(inputClassNames)} value={this.state.value}
|
||||
decimals={2} style={{ textAlign: 'right' }} step={0.01}
|
||||
stepModifier={1} onKeyDown={this._keyDown.bind(this)}
|
||||
onValueChange={this._updateValue.bind(this)} /> :
|
||||
<input type="text" value={formats.f2(this.state.value)}
|
||||
disabled className={cn('number-editor', 'greyed-out')}
|
||||
style={{ textAlign: 'right', cursor: 'inherit' }}/>
|
||||
}
|
||||
<span className={'unit-container'}>
|
||||
{units[m.getStoredUnitFor(name)]}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }} className={
|
||||
modValue ?
|
||||
isChangeValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
|
||||
''
|
||||
}>
|
||||
{formats.f2(modValue / 100) || 0}{isOverwrite ? '' : '%'}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,22 +2,33 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as _ from 'lodash';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import Modification from './Modification';
|
||||
import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions';
|
||||
import {
|
||||
getBlueprint,
|
||||
blueprintTooltip,
|
||||
setPercent,
|
||||
getPercent,
|
||||
setRandom,
|
||||
specialToolTip
|
||||
} from '../utils/BlueprintFunctions';
|
||||
|
||||
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
|
||||
return mod1.props.name.localeCompare(mod2.props.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
onChange: PropTypes.func.isRequired,
|
||||
modButton:PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,14 +41,24 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
||||
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
||||
this._rollWorst = this._rollWorst.bind(this);
|
||||
this._rollFifty = this._rollFifty.bind(this);
|
||||
this._rollRandom = this._rollRandom.bind(this);
|
||||
this._rollBest = this._rollBest.bind(this);
|
||||
this._rollExtreme = this._rollExtreme.bind(this);
|
||||
this._rollWorst = this._rollWorst.bind(this);
|
||||
this._reset = this._reset.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
|
||||
this.firstModId = null;
|
||||
this.firstBPLabel = null;// First item in mod menu
|
||||
this.lastModId = null;
|
||||
this.selectedModId = null;
|
||||
this.selectedSpecialId = null;
|
||||
this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
|
||||
this.modValDidChange = false; // used to determine if component update was caused by change in modification value.
|
||||
this._handleModChange = this._handleModChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
blueprintMenuOpened: false,
|
||||
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
|
||||
specialMenuOpened: false
|
||||
};
|
||||
}
|
||||
@@ -52,7 +73,6 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const blueprints = [];
|
||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
||||
const blueprint = getBlueprint(blueprintName, m);
|
||||
@@ -61,14 +81,18 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
// Grade is a string in the JSON so make it a number
|
||||
grade = Number(grade);
|
||||
const classes = cn('c', {
|
||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
||||
});
|
||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
||||
const key = blueprintName + ':' + grade;
|
||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
||||
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
|
||||
if (classes.indexOf('active') >= 0) this.selectedModId = key;
|
||||
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
|
||||
}
|
||||
if (blueprintGrades) {
|
||||
const thisLen = blueprintGrades.length;
|
||||
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
|
||||
this.lastModId = blueprintGrades[thisLen - 1].key;
|
||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
||||
}
|
||||
@@ -76,6 +100,64 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
return blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
|
||||
* @param {SyntheticEvent} event Event
|
||||
*
|
||||
*/
|
||||
_keyDown(event) {
|
||||
let className = null;
|
||||
let elemId = null;
|
||||
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
|
||||
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
|
||||
|
||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
||||
event.stopPropagation();
|
||||
if (elemId != null) {
|
||||
this.modItems[elemId].click();
|
||||
} else {
|
||||
event.currentTarget.click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key == 'Tab') {
|
||||
// Shift-Tab
|
||||
if(event.shiftKey) {
|
||||
if (elemId == this.firstModId && elemId != null) {
|
||||
// Initial modification menu
|
||||
event.preventDefault();
|
||||
this.modItems[this.lastModId].focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
|
||||
// shift-tab on first element in modifications menu. set focus to last number editor field if open
|
||||
event.preventDefault();
|
||||
this.modItems[this.lastNeId].lastChild.focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
|
||||
// shift-tab on button-inline-menu with no number editor
|
||||
event.preventDefault();
|
||||
event.currentTarget.parentElement.lastElementChild.focus();
|
||||
}
|
||||
} else {
|
||||
if (elemId == this.lastModId && elemId != null) {
|
||||
// Initial modification menu
|
||||
event.preventDefault();
|
||||
this.modItems[this.firstModId].focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
|
||||
// Experimental menu
|
||||
event.preventDefault();
|
||||
event.currentTarget.parentElement.firstElementChild.focus();
|
||||
return;
|
||||
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
|
||||
event.preventDefault();
|
||||
this.modItems[this.firstBPLabel].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render the specials
|
||||
* @param {Object} props React component properties
|
||||
@@ -86,15 +168,34 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const specials = [];
|
||||
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
|
||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
||||
const close = this._specialSelected.bind(this, null);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
||||
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
|
||||
continue;
|
||||
}
|
||||
const classes = cn('button-inline-menu', {
|
||||
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
|
||||
});
|
||||
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
|
||||
const close = this._specialSelected.bind(this, specialName);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
let tmp = {};
|
||||
if (m.blueprint.special) {
|
||||
tmp = m.blueprint.special;
|
||||
} else {
|
||||
tmp = undefined;
|
||||
}
|
||||
m.blueprint.special = Modifications.specials[specialName];
|
||||
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
|
||||
m.blueprint.special = tmp;
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
} else {
|
||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
}
|
||||
}
|
||||
}
|
||||
return specials;
|
||||
@@ -107,14 +208,26 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
*/
|
||||
_renderModifications(props) {
|
||||
const { m, onChange, ship } = props;
|
||||
const modifiableModifications = [];
|
||||
const modifications = [];
|
||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
||||
if (!Modifications.modifications[modName].hidden) {
|
||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
|
||||
const editable = modName !== 'fallofffromrange';
|
||||
const highlight = m.blueprint.grades[m.blueprint.grade].features[modName];
|
||||
this.lastNeId = modName;
|
||||
(editable && highlight ? modifiableModifications : modifications).push(
|
||||
<Modification key={ key } ship={ ship } m={ m } highlight={highlight}
|
||||
value={m.getPretty(modName) || 0} modItems={this.modItems}
|
||||
onChange={onChange} onKeyDown={this._keyDown} name={modName}
|
||||
editable={editable} handleModChange = {this._handleModChange} />
|
||||
);
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
|
||||
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
|
||||
modifications.sort(MODIFICATIONS_COMPARATOR);
|
||||
return modifiableModifications.concat(modifications);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,8 +249,9 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const blueprint = getBlueprint(fdname, m);
|
||||
blueprint.grade = grade;
|
||||
ship.setModuleBlueprint(m, blueprint);
|
||||
setPercent(ship, m, 100);
|
||||
|
||||
this.setState({ blueprintMenuOpened: false });
|
||||
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -168,11 +282,15 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a 'worst' roll within the information we have
|
||||
* Provide a '50%' roll within the information we have
|
||||
*/
|
||||
_rollWorst() {
|
||||
_rollFifty() {
|
||||
const { m, ship } = this.props;
|
||||
setWorst(ship, m);
|
||||
setPercent(ship, m, 50);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -182,6 +300,10 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
_rollRandom() {
|
||||
const { m, ship } = this.props;
|
||||
setRandom(ship, m);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -190,16 +312,22 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
*/
|
||||
_rollBest() {
|
||||
const { m, ship } = this.props;
|
||||
setBest(ship, m);
|
||||
setPercent(ship, m, 100);
|
||||
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an 'extreme' roll within the information we have
|
||||
* Provide a 'worst' roll within the information we have
|
||||
*/
|
||||
_rollExtreme() {
|
||||
_rollWorst() {
|
||||
const { m, ship } = this.props;
|
||||
setExtreme(ship, m);
|
||||
setPercent(ship, m, 0);
|
||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
||||
this._handleModChange(true);
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
@@ -210,10 +338,67 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
ship.clearModuleBlueprint(m);
|
||||
|
||||
this.selectedModId = null;
|
||||
this.selectedSpecialId = null;
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* set mod did change boolean
|
||||
* @param {boolean} b Boolean to determine if a change has been made to a module
|
||||
*/
|
||||
_handleModChange(b) {
|
||||
this.modValDidChange = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focus on first element in modifications menu
|
||||
* after it first mounts
|
||||
*/
|
||||
componentDidMount() {
|
||||
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
|
||||
if (firstEleCn.indexOf('select-group cap') >= 0) {
|
||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
||||
} else {
|
||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focus on first element in modifications menu
|
||||
* if component updates, unless update is due to value change
|
||||
* in a modification
|
||||
*/
|
||||
componentDidUpdate() {
|
||||
if (!this.modValDidChange) {
|
||||
if (this.modItems['modMainDiv'].children.length > 0) {
|
||||
if (this.modItems[this.selectedModId]) {
|
||||
this.modItems[this.selectedModId].focus();
|
||||
return;
|
||||
} else if (this.modItems[this.selectedSpecialId]) {
|
||||
this.modItems[this.selectedSpecialId].focus();
|
||||
return;
|
||||
}
|
||||
let firstEleCn = this.modItems['modMainDiv'].children[0].className;
|
||||
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
|
||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
||||
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
|
||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._handleModChange(false);// Need to reset if component update due to value change
|
||||
}
|
||||
}
|
||||
/**
|
||||
* set focus to the modification menu icon after mod menu is unmounted.
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.props.modButton) {
|
||||
this.props.modButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list
|
||||
* @return {React.Component} List
|
||||
@@ -226,66 +411,82 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
||||
const _rollBest = this._rollBest;
|
||||
const _rollExtreme = this._rollExtreme;
|
||||
const _rollFull = this._rollBest;
|
||||
const _rollWorst = this._rollWorst;
|
||||
const _rollFifty = this._rollFifty;
|
||||
const _rollRandom = this._rollRandom;
|
||||
const _reset = this._reset;
|
||||
|
||||
let blueprintLabel;
|
||||
let haveBlueprint = false;
|
||||
let blueprintTt;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
let blueprintCv;
|
||||
// TODO: Fix this to actually find the correct blueprint.
|
||||
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
|
||||
this.props.ship.clearModuleBlueprint(m);
|
||||
this.props.ship.clearModuleSpecial(m);
|
||||
}
|
||||
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
|
||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
haveBlueprint = true;
|
||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
|
||||
blueprintCv = getPercent(m);
|
||||
}
|
||||
|
||||
let specialLabel;
|
||||
let specialTt;
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
specialLabel = m.blueprint.special.name;
|
||||
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
|
||||
} else {
|
||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||
}
|
||||
|
||||
const specials = this._renderSpecials(this.props, this.context);
|
||||
|
||||
/**
|
||||
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
|
||||
* display correctly in cases where there are no specials (ex: AFMUs.)
|
||||
*/
|
||||
const showBlueprintsMenu = blueprintMenuOpened;
|
||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
||||
const showSpecialsMenu = specialMenuOpened;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showReset = !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showMods = !blueprintMenuOpened && !specialMenuOpened;
|
||||
|
||||
const showSpecialsMenu = specialMenuOpened && specials.length;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
|
||||
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
||||
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
||||
if (haveBlueprint) {
|
||||
this.firstBPLabel = blueprintLabel;
|
||||
} else {
|
||||
this.firstBPLabel = 'selectBP';
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
||||
>
|
||||
{ showBlueprintsMenu ? '' : haveBlueprint ?
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
|
||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
||||
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
|
||||
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
|
||||
{ showSpecialsMenu ? specials : null }
|
||||
{ showRolls || showReset ?
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
|
||||
{ showRolls ?
|
||||
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<tr>
|
||||
<td> { translate('roll') }: </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('mroll') }: </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
{ showReset ?
|
||||
<tr>
|
||||
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ showMods ? <hr /> : null }
|
||||
{ showMods ?
|
||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
||||
{ this._renderModifications(this.props) }
|
||||
|
||||
@@ -35,13 +35,13 @@ export default class Movement extends TranslatedComponent {
|
||||
return (
|
||||
<span id='movement'>
|
||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||
// Axes
|
||||
{/* Axes */}
|
||||
<path d="M150 250v300" strokeWidth='1'/>
|
||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||
<path d="M150 250l350 -200" strokeWidth='1'/>
|
||||
// End Arrow
|
||||
{/* End Arrow */}
|
||||
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
||||
// Axes arcs and arrows
|
||||
{/* Axes arcs and arrows */}
|
||||
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
|
||||
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
||||
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
||||
@@ -57,13 +57,13 @@ export default class Movement extends TranslatedComponent {
|
||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||
|
||||
// Speed
|
||||
{/* Speed */}
|
||||
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
|
||||
// Pitch
|
||||
{/* Pitch */}
|
||||
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
// Roll
|
||||
{/* Roll */}
|
||||
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
// Yaw
|
||||
{/* Yaw */}
|
||||
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
</svg>
|
||||
</span>);
|
||||
|
||||
@@ -5,7 +5,6 @@ import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
|
||||
/**
|
||||
* Generates an internationalization friendly weapon comparator that will
|
||||
@@ -42,6 +41,63 @@ export function weaponComparator(translate, propComparator, desc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tooltip that shows damage by type.
|
||||
* @param {function} translate Translation function
|
||||
* @param {Object} formats Object that holds format functions
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
||||
* @returns {Array} Tooltip
|
||||
*/
|
||||
function getSDpsTooltip(translate, formats, sdpsObject) {
|
||||
return Object.keys(sdpsObject).filter(key => sdpsObject[key])
|
||||
.map(key => {
|
||||
return (
|
||||
<div key={key}>
|
||||
{translate(key) + ' ' + formats.f1(sdpsObject[key])}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||
* @param {function} translate Translation function
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
||||
* @returns {Object} Data object
|
||||
*/
|
||||
function getSDpsData(translate, sdpsObject) {
|
||||
return Object.keys(sdpsObject).map(key => {
|
||||
return {
|
||||
value: Math.round(sdpsObject[key]),
|
||||
label: translate(key)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all damage of `add` onto `addOn`.
|
||||
* @param {Calc.SDps} addOn Object that holds sdps split up by type (will be mutated)
|
||||
* @param {Calc.SDps} add Object that holds sdps split up by type
|
||||
*/
|
||||
function addSDps(addOn, add) {
|
||||
Object.keys(addOn).map(k => addOn[k] += (add[k] ? add[k] : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the overall sdps of an sdps object.
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps spluit up by type
|
||||
*/
|
||||
function sumSDps(sdpsObject) {
|
||||
if (sdpsObject.total) {
|
||||
return sdpsObject.total;
|
||||
}
|
||||
|
||||
return Object.keys(sdpsObject).reduce(
|
||||
(acc, k) => acc + (sdpsObject[k] ? sdpsObject[k] : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offence information
|
||||
* Offence information consists of four panels:
|
||||
@@ -70,7 +126,7 @@ export default class Offence extends TranslatedComponent {
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
||||
this.state = {
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
damage
|
||||
@@ -144,51 +200,36 @@ export default class Offence extends TranslatedComponent {
|
||||
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, wep);
|
||||
|
||||
let absoluteShieldsSDps = 0;
|
||||
let explosiveShieldsSDps = 0;
|
||||
let kineticShieldsSDps = 0;
|
||||
let thermalShieldsSDps = 0;
|
||||
let absoluteArmourSDps = 0;
|
||||
let explosiveArmourSDps = 0;
|
||||
let kineticArmourSDps = 0;
|
||||
let thermalArmourSDps = 0;
|
||||
|
||||
let totalSEps = 0;
|
||||
let totalSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
||||
let shieldsSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
||||
let armourSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
||||
|
||||
const rows = [];
|
||||
for (let i = 0; i < damage.length; i++) {
|
||||
const weapon = damage[i];
|
||||
|
||||
totalSEps += weapon.seps;
|
||||
absoluteShieldsSDps += weapon.sdps.shields.absolute;
|
||||
explosiveShieldsSDps += weapon.sdps.shields.explosive;
|
||||
kineticShieldsSDps += weapon.sdps.shields.kinetic;
|
||||
thermalShieldsSDps += weapon.sdps.shields.thermal;
|
||||
absoluteArmourSDps += weapon.sdps.armour.absolute;
|
||||
explosiveArmourSDps += weapon.sdps.armour.explosive;
|
||||
kineticArmourSDps += weapon.sdps.armour.kinetic;
|
||||
thermalArmourSDps += weapon.sdps.armour.thermal;
|
||||
addSDps(totalSDpsObject, weapon.sdps.base);
|
||||
addSDps(shieldsSDpsObject, weapon.sdps.shields);
|
||||
addSDps(armourSDpsObject, weapon.sdps.armour);
|
||||
|
||||
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
|
||||
|
||||
const effectivenessShieldsTooltipDetails = [];
|
||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
||||
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
|
||||
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
|
||||
|
||||
const effectiveShieldsSDpsTooltipDetails = [];
|
||||
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
|
||||
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
|
||||
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
|
||||
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
|
||||
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
||||
|
||||
const effectivenessArmourTooltipDetails = [];
|
||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
||||
const effectiveArmourSDpsTooltipDetails = [];
|
||||
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
|
||||
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
|
||||
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
|
||||
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
|
||||
|
||||
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
||||
|
||||
rows.push(
|
||||
<tr key={weapon.id}>
|
||||
@@ -199,27 +240,30 @@ export default class Offence extends TranslatedComponent {
|
||||
{weapon.classRating} {translate(weapon.name)}
|
||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
||||
</td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
||||
|
||||
<td className='ri'><span>{formats.f1(weapon.effectiveness.shields.dpe)}</span></td>
|
||||
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
||||
|
||||
<td className='ri'><span>{formats.f1(weapon.effectiveness.armour.dpe)}</span></td>
|
||||
</tr>);
|
||||
}
|
||||
}
|
||||
|
||||
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
|
||||
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
|
||||
const totalSDps = sumSDps(totalSDpsObject);
|
||||
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
|
||||
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
|
||||
|
||||
const shieldsSDpsData = [];
|
||||
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
|
||||
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
|
||||
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
|
||||
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
|
||||
const totalShieldsSDps = sumSDps(shieldsSDpsObject);
|
||||
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
|
||||
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
|
||||
|
||||
const armourSDpsData = [];
|
||||
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
|
||||
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
|
||||
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
|
||||
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
|
||||
const totalArmourSDps = sumSDps(armourSDpsObject);
|
||||
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
|
||||
const armourSDpsData = getSDpsData(translate, armourSDpsObject);
|
||||
|
||||
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
@@ -227,24 +271,43 @@ export default class Offence extends TranslatedComponent {
|
||||
return (
|
||||
<span id='offence'>
|
||||
<div className='group full'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='1'>{translate('overall')}</th>
|
||||
<th colSpan='3'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='3'>{translate('opponent\'s armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
||||
|
||||
<th className='sortable'>{'dpe'}</th>
|
||||
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
||||
|
||||
<th className='sortable'>{'dpe'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
{rows.length > 0 &&
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('offence metrics')}</h2>
|
||||
@@ -254,6 +317,10 @@ export default class Offence extends TranslatedComponent {
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
|
||||
<PieChart data={totalSDpsData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
|
||||
<PieChart data={shieldsSDpsData} />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import PowerManagement from './PowerManagement';
|
||||
@@ -176,7 +174,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('tab_defence')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
@@ -1,97 +1,92 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
|
||||
/**
|
||||
* A pie chart
|
||||
*/
|
||||
export default class PieChart extends Component {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.pie = d3.pie().value((d) => d.value);
|
||||
this.colors = CORIOLIS_COLOURS;
|
||||
this.arc = d3.arc();
|
||||
this.arc.innerRadius(0);
|
||||
|
||||
this.state = {
|
||||
dimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a slice of the pie chart
|
||||
* @param {Object} d the data for this slice
|
||||
* @param {number} i the index of this slice
|
||||
* @returns {Object} the SVG for the slice
|
||||
*/
|
||||
sliceGenerator(d, i) {
|
||||
if (!d || d.value == 0) {
|
||||
// Ignore 0 values
|
||||
return null;
|
||||
}
|
||||
|
||||
const { width, height } = this.state.dimensions;
|
||||
const { data } = this.props;
|
||||
|
||||
// Push the labels further out from the centre of the slice
|
||||
let [labelX, labelY] = this.arc.centroid(d);
|
||||
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
|
||||
|
||||
// Put the keys in a line with equal spacing
|
||||
const nonZeroItems = data.filter(d => d.value != 0).length;
|
||||
const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
|
||||
const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
|
||||
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
|
||||
|
||||
return (
|
||||
<g key={`group-${i}`}>
|
||||
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
|
||||
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
|
||||
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component
|
||||
* @returns {object} Markup
|
||||
*/
|
||||
render() {
|
||||
const { width, height } = this.state.dimensions;
|
||||
const pie = this.pie(this.props.data),
|
||||
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||
|
||||
this.arc.outerRadius(width * 0.4);
|
||||
|
||||
return (
|
||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
||||
<div width={width} height={width}>
|
||||
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
||||
<g transform={translate}>
|
||||
{pie.map((d, i) => this.sliceGenerator(d, i))}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
|
||||
/**
|
||||
* A pie chart
|
||||
*/
|
||||
export default class PieChart extends Component {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.pie = d3.pie().value((d) => d.value);
|
||||
this.colors = CORIOLIS_COLOURS;
|
||||
this.arc = d3.arc();
|
||||
this.arc.innerRadius(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a slice of the pie chart
|
||||
* @param {Object} d the data for this slice
|
||||
* @param {number} i the index of this slice
|
||||
* @param {number} width the current width of the parent container
|
||||
* @returns {Object} the SVG for the slice
|
||||
*/
|
||||
sliceGenerator(d, i, width) {
|
||||
if (!d || d.value == 0) {
|
||||
// Ignore 0 values
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data } = this.props;
|
||||
|
||||
// Push the labels further out from the centre of the slice
|
||||
let [labelX, labelY] = this.arc.centroid(d);
|
||||
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
|
||||
|
||||
// Put the keys in a line with equal spacing
|
||||
const nonZeroItems = data.filter(d => d.value != 0).length;
|
||||
const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
|
||||
const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
|
||||
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
|
||||
|
||||
return (
|
||||
<g key={`group-${i}`}>
|
||||
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
|
||||
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
|
||||
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component
|
||||
* @returns {object} Markup
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ContainerDimensions>
|
||||
{ ({ width }) => {
|
||||
const pie = this.pie(this.props.data),
|
||||
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||
|
||||
this.arc.outerRadius(width * 0.4);
|
||||
return (
|
||||
<div width={width} height={width}>
|
||||
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
||||
<g transform={translate}>
|
||||
{pie.map((d, i) => this.sliceGenerator(d, i, width))}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import { autoBind } from 'react-extras';
|
||||
|
||||
/**
|
||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||
@@ -18,6 +13,9 @@ export default class Pips extends TranslatedComponent {
|
||||
sys: PropTypes.number.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
wep: PropTypes.number.isRequired,
|
||||
mcSys: PropTypes.number.isRequired,
|
||||
mcEng: PropTypes.number.isRequired,
|
||||
mcWep: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -28,9 +26,7 @@ export default class Pips extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
const { sys, eng, wep } = props;
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,30 +70,21 @@ export default class Pips extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a click
|
||||
* @param {string} which Which item was clicked
|
||||
*/
|
||||
onClick(which) {
|
||||
if (which == 'SYS') {
|
||||
this._incSys();
|
||||
} else if (which == 'ENG') {
|
||||
this._incEng();
|
||||
} else if (which == 'WEP') {
|
||||
this._incWep();
|
||||
} else if (which == 'RST') {
|
||||
this._reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the capacitor
|
||||
*/
|
||||
_reset() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
if (sys != 2 || eng != 2 || wep != 2) {
|
||||
_reset(isMc) {
|
||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
||||
if (isMc) {
|
||||
if (mcSys || mcEng || mcWep) {
|
||||
sys -= mcSys;
|
||||
eng -= mcEng;
|
||||
wep -= mcWep;
|
||||
this.props.onChange(sys, eng, wep, 0, 0, 0);
|
||||
}
|
||||
} else if (sys != 2 || eng != 2 || wep != 2) {
|
||||
sys = eng = wep = 2;
|
||||
this.props.onChange(sys, eng, wep);
|
||||
this.props.onChange(sys + mcSys, eng + mcEng, wep + mcWep, mcSys, mcEng, mcWep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,151 +92,133 @@ export default class Pips extends TranslatedComponent {
|
||||
* 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);
|
||||
}
|
||||
this._inc('sys', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
this._inc('eng', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the WEP capacitor
|
||||
*/
|
||||
_incWep() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
this._inc('wep', false);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
_wrapMcClick(key) {
|
||||
return (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (key == 'rst') {
|
||||
this._reset(true);
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
eng -= 1;
|
||||
wep += 1;
|
||||
} else if (eng == 0) {
|
||||
sys -= 1;
|
||||
wep += 1;
|
||||
this._inc(key, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases a given capacitor
|
||||
* @param {String} key Pip name to increase (one of 'sys', 'eng', 'wep')
|
||||
* @param {Boolean} isMc True when increase is by multi crew
|
||||
*/
|
||||
_inc(key, isMc) {
|
||||
if (!['sys', 'eng', 'wep'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
||||
let mc = key == 'sys' ? mcSys : (key == 'eng' ? mcEng : mcWep);
|
||||
let pips = this.props[key] - mc;
|
||||
let other1 = key == 'sys' ? eng - mcEng : sys - mcSys;
|
||||
let other2 = key == 'wep' ? eng - mcEng : wep - mcWep;
|
||||
|
||||
const required = Math.min(1, 4 - mc - pips);
|
||||
if (isMc) {
|
||||
// We can only set full pips in multi-crew also we can only set two pips
|
||||
if (required > 0.5 && mcSys + mcEng + mcWep < 2) {
|
||||
if (key == 'sys') {
|
||||
mcSys += 1;
|
||||
} else if (key == 'eng') {
|
||||
mcEng += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
eng -= 0.5;
|
||||
wep += 1;
|
||||
mcWep += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
} else if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (other1 > other2) {
|
||||
other1 -= 0.5;
|
||||
} else {
|
||||
other2 -= 0.5;
|
||||
}
|
||||
pips += 0.5;
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (other1 == 0) {
|
||||
other2 -= 1;
|
||||
} else if (other2 == 0) {
|
||||
other1 -= 1;
|
||||
} else {
|
||||
other1 -= 0.5;
|
||||
other2 -= 0.5;
|
||||
}
|
||||
pips += 1;
|
||||
}
|
||||
}
|
||||
|
||||
sys = mcSys + (key == 'sys' ? pips : other1);
|
||||
eng = mcEng + (key == 'eng' ? pips : (key == 'sys' ? other1 : other2));
|
||||
wep = mcWep + (key == 'wep' ? pips : other2);
|
||||
this.props.onChange(sys, eng, wep, mcSys, mcEng, mcWep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the rendering for pips
|
||||
* @param {int} sys the SYS pips
|
||||
* @param {int} eng the ENG pips
|
||||
* @param {int} wep the WEP pips
|
||||
* @param {Number} sys the SYS pips
|
||||
* @param {Number} eng the ENG pips
|
||||
* @param {Number} wep the WEP pips
|
||||
* @param {Number} mcSys SYS pips from multi-crew
|
||||
* @param {Number} mcEng ENG pips from multi-crew
|
||||
* @param {Number} mcWep WEP pips from multi-crew
|
||||
* @returns {Object} Object containing the rendering for the pips
|
||||
*/
|
||||
_renderPips(sys, eng, wep) {
|
||||
const pipsSvg = {};
|
||||
_renderPips(sys, eng, wep, mcSys, mcEng, mcWep) {
|
||||
const pipsSvg = {
|
||||
SYS: [],
|
||||
ENG: [],
|
||||
WEP: [],
|
||||
};
|
||||
|
||||
// SYS
|
||||
pipsSvg['SYS'] = [];
|
||||
for (let i = 0; i < Math.floor(sys); i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (sys > Math.floor(sys)) {
|
||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
// Multi-crew pipsSettings actually are included in the overall pip count therefore
|
||||
// we can consider [0, sys - mcSys] as normal pipsSettings whilst [sys - mcSys, sys]
|
||||
// are the multi-crew pipsSettings in what follows.
|
||||
|
||||
// ENG
|
||||
pipsSvg['ENG'] = [];
|
||||
for (let i = 0; i < Math.floor(eng); i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (eng > Math.floor(eng)) {
|
||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
let pipsSettings = {
|
||||
SYS: [sys, mcSys],
|
||||
ENG: [eng, mcEng],
|
||||
WEP: [wep, mcWep],
|
||||
};
|
||||
|
||||
// WEP
|
||||
pipsSvg['WEP'] = [];
|
||||
for (let i = 0; i < Math.floor(wep); i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (wep > Math.floor(wep)) {
|
||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
||||
for (let pipName in pipsSettings) {
|
||||
let [pips, mcPips] = pipsSettings[pipName];
|
||||
for (let i = 0; i < Math.floor(pips - mcPips); i++) {
|
||||
pipsSvg[pipName].push(<Pip key={i} className='full' />);
|
||||
}
|
||||
if (pips > Math.floor(pips)) {
|
||||
pipsSvg[pipName].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = pips - mcPips; i < Math.floor(pips); i++) {
|
||||
pipsSvg[pipName].push(<Pip key={i} className='mc' />);
|
||||
}
|
||||
for (let i = Math.floor(pips + 0.5); i < 4; i++) {
|
||||
pipsSvg[pipName].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
}
|
||||
|
||||
return pipsSvg;
|
||||
@@ -260,15 +229,11 @@ export default class Pips extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { sys, eng, wep } = this.props;
|
||||
const { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
||||
|
||||
const onSysClicked = this.onClick.bind(this, 'SYS');
|
||||
const onEngClicked = this.onClick.bind(this, 'ENG');
|
||||
const onWepClicked = this.onClick.bind(this, 'WEP');
|
||||
const onRstClicked = this.onClick.bind(this, 'RST');
|
||||
|
||||
const pipsSvg = this._renderPips(sys, eng, wep);
|
||||
const pipsSvg = this._renderPips(sys, eng, wep, mcSys, mcEng, mcWep);
|
||||
return (
|
||||
<span id='pips'>
|
||||
<table>
|
||||
@@ -276,20 +241,38 @@ export default class Pips extends TranslatedComponent {
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
|
||||
<td className='clickable' onClick={() => this._inc('eng')}
|
||||
onContextMenu={this._wrapMcClick('eng')}>{pipsSvg['ENG']}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
|
||||
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
|
||||
<td className='clickable' onClick={this._incSys}
|
||||
onContextMenu={this._wrapMcClick('sys')}>{pipsSvg['SYS']}</td>
|
||||
<td className='clickable' onClick={this._incEng}
|
||||
onContextMenu={this._wrapMcClick('eng')}>{translate('ENG')}</td>
|
||||
<td className='clickable' onClick={this._incWep}
|
||||
onContextMenu={this._wrapMcClick('wep')}>{pipsSvg['WEP']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
|
||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
||||
<td className='clickable' onClick={this._incSys}
|
||||
onContextMenu={this._wrapMcClick('sys')}>{translate('SYS')}</td>
|
||||
<td className='clickable' onClick={this._reset.bind(this, false)}>
|
||||
{translate('RST')}
|
||||
</td>
|
||||
<td className='clickable' onClick={this._incWep}
|
||||
onContextMenu={this._wrapMcClick('wep')}>{translate('WEP')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable secondary' onClick={this._wrapMcClick('rst')}
|
||||
onMouseEnter={termtip.bind(null, 'PHRASE_MULTI_CREW_CAPACITOR_POINTS')}
|
||||
onMouseLeave={tooltip.bind(null, null)}>
|
||||
{translate('RST')}
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -32,7 +32,6 @@ function bandText(val, index, wattScale) {
|
||||
* Renders the SVG to simulate in-game power bands
|
||||
*/
|
||||
export default class PowerBands extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
bands: PropTypes.array.isRequired,
|
||||
available: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { Rocket } from './SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
@@ -15,22 +15,34 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
pips: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* The ShipSummaryTable constructor
|
||||
* @param {Object} props The props
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.didContextChange = this.didContextChange.bind(this);
|
||||
this.state = {
|
||||
shieldColour: 'blue'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table
|
||||
* @return {React.Component} Summary table
|
||||
*/
|
||||
render() {
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
const { ship, cargo, fuel, pips } = this.props;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let translate = language.translate;
|
||||
let u = language.units;
|
||||
let formats = language.formats;
|
||||
let { time, int, round, f1, f2 } = formats;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
const shieldGenerator = ship.findInternalByGroup('sg');
|
||||
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
|
||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
||||
@@ -38,66 +50,167 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
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 canJump = ship.getSlotStatus(ship.standard[2]) == 3;
|
||||
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
|
||||
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
|
||||
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
|
||||
const armourMetrics = Calc.armourMetrics(ship);
|
||||
let shieldColour = 'blue';
|
||||
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
|
||||
shieldColour = 'green';
|
||||
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
|
||||
shieldColour = 'purple';
|
||||
}
|
||||
this.state = {
|
||||
shieldColour
|
||||
};
|
||||
return <div id='summary'>
|
||||
<table id='summaryTable'>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5}>{translate('jump range')}</th>
|
||||
<th rowSpan={2}>{translate('shield')}</th>
|
||||
<th rowSpan={2}>{translate('integrity')}</th>
|
||||
<th rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('EPS')}</th>
|
||||
<th rowSpan={2}>{translate('TTD')}</th>
|
||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2}>{translate('passengers')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||
<th rowSpan={2}>{translate('crew')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft'>{translate('max')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
<th>{translate('total unladen')}</th>
|
||||
<th>{translate('total laden')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
||||
<td>{ship.passengerCapacity}</td>
|
||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{ship.crew}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div style={{display: "table", width: "100%"}}>
|
||||
<div style={{display: "table-row"}}>
|
||||
<table className={'summaryTable'}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5} className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('jump range')}</th>
|
||||
<th rowSpan={2}>{translate('shield')}</th>
|
||||
<th rowSpan={2}>{translate('integrity')}</th>
|
||||
<th rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('EPS')}</th>
|
||||
<th rowSpan={2}>{translate('TTD')}</th>
|
||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{translate('pax')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||
<th rowSpan={2}>{translate('crew')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
|
||||
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className={ cn({ 'lft': true, 'bg-warning-disabled': !canJump }) }>{translate('max')}</th>
|
||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('unladen')}</th>
|
||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('laden')}</th>
|
||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total unladen')}</th>
|
||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total laden')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{ f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
||||
<td>{ship.passengerCapacity}</td>
|
||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{ship.crew}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
|
||||
<td>{formats.pct(restingHeat)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={'summaryTable'}>
|
||||
<thead className={this.state.shieldColour}>
|
||||
<tr>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
|
||||
<th colSpan={4} className='lft'>{translate('resistance')}</th>
|
||||
|
||||
<th colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th></th>
|
||||
|
||||
<th className={'bordered'}>{`${translate('absolute')}`}</th>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
||||
<td>{formats.pct1(ship.shieldExplRes)}</td>
|
||||
<td>{formats.pct1(ship.shieldKinRes)}</td>
|
||||
<td>{formats.pct1(ship.shieldThermRes)}</td>
|
||||
<td></td>
|
||||
|
||||
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary / sgMetrics.explosive.base : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.kinetic.base : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.thermal.base : 0)}{u.MJ}</td>
|
||||
<td></td>
|
||||
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
|
||||
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
|
||||
<th colSpan={4} className='lft'>{translate('resistance')}</th>
|
||||
|
||||
<th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th>{`${translate('caustic')}`}</th>
|
||||
|
||||
<th className={'bordered'}>{`${translate('absolute')}`}</th>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th>{`${translate('caustic')}`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
||||
<td>{formats.pct1(ship.hullExplRes)}</td>
|
||||
<td>{formats.pct1(ship.hullKinRes)}</td>
|
||||
<td>{formats.pct1(ship.hullThermRes)}</td>
|
||||
<td>{formats.pct1(ship.hullCausRes)}</td>
|
||||
<td>{int(armourMetrics.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
|
||||
<td>{int(armourMetrics.total/ armourMetrics.kinetic.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
|
||||
<td>{int(armourMetrics.total/ armourMetrics.caustic.total)}</td>
|
||||
<td>{int(armourMetrics.modulearmour)}</td>
|
||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +1,386 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MARGIN_LR = 8; // Left/ Right margin
|
||||
|
||||
/**
|
||||
* Horizontal Slider
|
||||
*/
|
||||
export default class Slider extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
axis: false,
|
||||
min: 0,
|
||||
max: 1,
|
||||
scale: 1 // SVG render scale
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
axis: PropTypes.bool,
|
||||
axisUnit: PropTypes.string,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onResize: PropTypes.func,
|
||||
percent: PropTypes.number.isRequired,
|
||||
scale: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._down = this._down.bind(this);
|
||||
this._move = this._move.bind(this);
|
||||
this._up = this._up.bind(this);
|
||||
this._updatePercent = this._updatePercent.bind(this);
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
|
||||
this.state = { width: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_down(event) {
|
||||
let rect = event.currentTarget.getBoundingClientRect();
|
||||
this.left = rect.left;
|
||||
this.width = rect.width;
|
||||
this._move(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage on move
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_move(event) {
|
||||
if(this.width !== null && this.left != null) {
|
||||
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
||||
event.preventDefault();
|
||||
this._updatePercent(clientX - this.left, this.width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch up handler
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_up(event) {
|
||||
event.preventDefault();
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is still dragging
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_enter(event) {
|
||||
if(event.buttons !== 1) {
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage
|
||||
* @param {number} pos Slider drag position
|
||||
* @param {number} width Slider width
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_updatePercent(pos, width) {
|
||||
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimenions from rendered DOM
|
||||
*/
|
||||
_updateDimensions() {
|
||||
this.setState({
|
||||
outerWidth: this.node.getBoundingClientRect().width
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners when about to mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
if (this.props.onResize) {
|
||||
this.resizeListener = this.props.onResize(this._updateDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger DOM updates on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.resizeListener) {
|
||||
this.resizeListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slider
|
||||
* @return {React.Component} The slider
|
||||
*/
|
||||
render() {
|
||||
let outerWidth = this.state.outerWidth;
|
||||
let { axis, axisUnit, min, max, scale } = this.props;
|
||||
|
||||
let style = {
|
||||
width: '100%',
|
||||
height: axis ? '2.5em' : '1.5em',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
|
||||
if (!outerWidth) {
|
||||
return <svg style={style} ref={node => this.node = node} />;
|
||||
}
|
||||
|
||||
let margin = MARGIN_LR * scale;
|
||||
let width = outerWidth - (margin * 2);
|
||||
let pctPos = width * this.props.percent;
|
||||
|
||||
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
|
||||
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
||||
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
||||
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} />
|
||||
{axis && <g style={{ fontSize: '.7em' }}>
|
||||
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||
</g>}
|
||||
</svg>;
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const MARGIN_LR = 8; // Left/ Right margin
|
||||
|
||||
/**
|
||||
* Horizontal Slider
|
||||
*/
|
||||
export default class Slider extends React.Component {
|
||||
|
||||
static defaultProps = {
|
||||
axis: false,
|
||||
min: 0,
|
||||
max: 1,
|
||||
scale: 1 // SVG render scale
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
axis: PropTypes.bool,
|
||||
axisUnit: PropTypes.string,// units (T, M, etc.)
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,// function which determins percent value
|
||||
onResize: PropTypes.func,
|
||||
percent: PropTypes.number.isRequired,// value of slider
|
||||
scale: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._down = this._down.bind(this);
|
||||
this._move = this._move.bind(this);
|
||||
this._up = this._up.bind(this);
|
||||
this._keyup = this._keyup.bind(this);
|
||||
this._keydown = this._keydown.bind(this);
|
||||
this._touchstart = this._touchstart.bind(this);
|
||||
this._touchend = this._touchend.bind(this);
|
||||
|
||||
this._updatePercent = this._updatePercent.bind(this);
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
|
||||
this.state = { width: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_down(event) {
|
||||
let rect = event.currentTarget.getBoundingClientRect();
|
||||
this.left = rect.left;
|
||||
this.width = rect.width;
|
||||
this._move(event);
|
||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage on move
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_move(event) {
|
||||
if(this.width !== null && this.left != null) {
|
||||
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
|
||||
event.preventDefault();
|
||||
this._updatePercent(clientX - this.left, this.width);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Mouse/Touch up handler
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_up(event) {
|
||||
this.sliderInputBox.sliderVal.focus();
|
||||
clearTimeout(this.touchStartTimer);
|
||||
event.preventDefault();
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Key up handler for keyboard.
|
||||
* display the number field then set focus to it
|
||||
* when "Enter" key is pressed
|
||||
* @param {Event} event Keyboard event
|
||||
*/
|
||||
_keyup(event) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
this.sliderInputBox._setDisplay('block');
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Key down handler
|
||||
* increment slider position by +/- 1 when right/left arrow key is pressed or held
|
||||
* @param {Event} event Keyboard even
|
||||
*/
|
||||
_keydown(event) {
|
||||
let newVal = this.props.percent * this.props.max;
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
newVal += 1;
|
||||
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
|
||||
return;
|
||||
case 'ArrowLeft':
|
||||
newVal -= 1;
|
||||
if (newVal >= 0) this.props.onChange(newVal / this.props.max);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch start handler
|
||||
* @param {Event} event DOM Event
|
||||
*
|
||||
*/
|
||||
_touchstart(event) {
|
||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Touch end handler
|
||||
* @param {Event} event DOM Event
|
||||
*
|
||||
*/
|
||||
_touchend(event) {
|
||||
this.sliderInputBox.sliderVal.focus();
|
||||
clearTimeout(this.touchStartTimer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user is still dragging
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_enter(event) {
|
||||
if(event.buttons !== 1) {
|
||||
this.left = null;
|
||||
this.width = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the slider percentage
|
||||
* @param {number} pos Slider drag position
|
||||
* @param {number} width Slider width
|
||||
* @param {Event} event DOM Event
|
||||
*/
|
||||
_updatePercent(pos, width) {
|
||||
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimenions from rendered DOM
|
||||
*/
|
||||
_updateDimensions() {
|
||||
this.setState({
|
||||
outerWidth: this.node.getBoundingClientRect().width
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners when about to mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
if (this.props.onResize) {
|
||||
this.resizeListener = this.props.onResize(this._updateDimensions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger DOM updates on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.resizeListener) {
|
||||
this.resizeListener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slider
|
||||
* @return {React.Component} The slider
|
||||
*/
|
||||
render() {
|
||||
let outerWidth = this.state.outerWidth;
|
||||
let { axis, axisUnit, min, max, scale } = this.props;
|
||||
let style = {
|
||||
width: '100%',
|
||||
height: axis ? '2.5em' : '1.5em',
|
||||
boxSizing: 'border-box'
|
||||
};
|
||||
if (!outerWidth) {
|
||||
return <svg style={style} ref={node => this.node = node} />;
|
||||
}
|
||||
let margin = MARGIN_LR * scale;
|
||||
let width = outerWidth - (margin * 2);
|
||||
let pctPos = width * this.props.percent;
|
||||
return <div><svg
|
||||
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
|
||||
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
||||
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
||||
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
|
||||
{axis && <g style={{ fontSize: '.7em' }}>
|
||||
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
|
||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||
</g>}
|
||||
</svg>
|
||||
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
|
||||
onChange={this.props.onChange}
|
||||
percent={this.props.percent}
|
||||
axisUnit={this.props.axisUnit}
|
||||
scale={this.props.scale}
|
||||
max={this.props.max}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
|
||||
**/
|
||||
class TextInputBox extends React.Component {
|
||||
static propTypes = {
|
||||
axisUnit: PropTypes.string,// units (T, M, etc.)
|
||||
max: PropTypes.number,
|
||||
onChange: PropTypes.func.isRequired,// function which determins percent value
|
||||
percent: PropTypes.number.isRequired,// value of slider
|
||||
scale: PropTypes.number
|
||||
};
|
||||
/**
|
||||
* Determine if the user is still dragging
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._handleFocus = this._handleFocus.bind(this);
|
||||
this._handleBlur = this._handleBlur.bind(this);
|
||||
this._handleChange = this._handleChange.bind(this);
|
||||
this._keyup = this._keyup.bind(this);
|
||||
this.state = this._getInitialState();
|
||||
}
|
||||
/**
|
||||
* Update input value if slider changes will change props/state
|
||||
* @param {Object} nextProps React Component properites
|
||||
* @param {Object} nextState React Component state values
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextState) {
|
||||
let nextValue = nextProps.percent * nextProps.max;
|
||||
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
|
||||
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
|
||||
this.setState({ inputValue: nextValue });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update slider textbox visibility/values if changes are made to slider
|
||||
* @param {Object} prevProps React Component properites
|
||||
* @param {Object} prevState React Component state values
|
||||
*/
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
|
||||
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
|
||||
}
|
||||
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
|
||||
// they chose a different module
|
||||
this.setState({ inputValue: this.props.max });
|
||||
}
|
||||
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
|
||||
this.props.onChange(this.state.inputValue / this.props.max);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set initial state for the textbox.
|
||||
* We may want to rethink this to
|
||||
* try and make it a stateless component
|
||||
* @returns {object} React state object with initial values set
|
||||
*/
|
||||
_getInitialState() {
|
||||
return {
|
||||
divStyle: { display:'none' },
|
||||
inputStyle: { width:'4em' },
|
||||
labelStyle: { marginLeft: '.1em' },
|
||||
maxLength:5,
|
||||
size:5,
|
||||
min:0,
|
||||
tabIndex:-1,
|
||||
type:'number',
|
||||
readOnly: true,
|
||||
inputValue: this.props.percent * this.props.max
|
||||
};
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {string} val block or none
|
||||
*/
|
||||
_setDisplay(val) {
|
||||
this.setState({
|
||||
divStyle: { display:val }
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update the input value
|
||||
* when textbox gets focus
|
||||
*/
|
||||
_handleFocus() {
|
||||
this.setState({
|
||||
inputValue:this._getValue()
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update inputValue when textbox loses focus
|
||||
*/
|
||||
_handleBlur() {
|
||||
this._setDisplay('none');
|
||||
if (this.state.inputValue !== '') {
|
||||
this.props.onChange(this.state.inputValue / this.props.max);
|
||||
} else {
|
||||
this.setState({
|
||||
inputValue: this.props.percent * this.props.max
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the value in the text box
|
||||
* @returns {number} inputValue Value of the input box
|
||||
*/
|
||||
_getValue() {
|
||||
return this.state.inputValue;
|
||||
}
|
||||
/**
|
||||
* Update and set limits on input box
|
||||
* values depending on what user
|
||||
* has selected
|
||||
*
|
||||
* @param {SyntheticEvent} event ReactJs onChange event
|
||||
*/
|
||||
_handleChange(event) {
|
||||
if (event.target.value < 0) {
|
||||
this.setState({ inputValue: 0 });
|
||||
} else if (event.target.value <= this.props.max) {
|
||||
this.setState({ inputValue: event.target.value });
|
||||
} else {
|
||||
this.setState({ inputValue: this.props.max });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Key up handler for input field.
|
||||
* If user hits Enter key, blur/close the input field
|
||||
* @param {Event} event Keyboard event
|
||||
*/
|
||||
_keyup(event) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
this.sliderVal.blur();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the value in the text box
|
||||
* @return {React.Component} Text Input component for Slider
|
||||
*/
|
||||
render() {
|
||||
let { axisUnit, onChange, percent, scale } = this.props;
|
||||
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
/**
|
||||
* Abstract Slot
|
||||
@@ -40,6 +39,8 @@ export default class Slot extends TranslatedComponent {
|
||||
|
||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this.slotDiv = null;
|
||||
}
|
||||
|
||||
// Must be implemented by subclasses:
|
||||
@@ -73,6 +74,22 @@ export default class Slot extends TranslatedComponent {
|
||||
this.props.onSelect(null,null);
|
||||
}
|
||||
|
||||
/** Key Down handler
|
||||
* @param {SyntheticEvent} event Event
|
||||
* ToDo: see if this can be moved up
|
||||
* we do more or less the same thing
|
||||
* in every section when Enter key is pressed
|
||||
* on a focusable item
|
||||
*
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
if(event.target.className == 'r') {
|
||||
this._toggleModifications();
|
||||
}
|
||||
this.props.onOpen(event);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Render the slot
|
||||
* @return {React.Component} The slot
|
||||
@@ -104,16 +121,18 @@ export default class Slot extends TranslatedComponent {
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
modButton = {this.modButton}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
ship={ship}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
slotDiv = {this.slotDiv}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +140,7 @@ export default class Slot extends TranslatedComponent {
|
||||
// TODO: implement touch dragging
|
||||
|
||||
return (
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
|
||||
<div className='details-container'>
|
||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
||||
{slotDetails}
|
||||
@@ -131,6 +150,7 @@ export default class Slot extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,8 @@ export default class SlotSection extends TranslatedComponent {
|
||||
onCargoChange: PropTypes.func.isRequired,
|
||||
onFuelChange: PropTypes.func.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
togglePwr: PropTypes.func
|
||||
togglePwr: PropTypes.func,
|
||||
sectionMenuRefs: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,10 @@ export default class SlotSection extends TranslatedComponent {
|
||||
super(props);
|
||||
this.sectionId = sectionId;
|
||||
this.sectionName = sectionName;
|
||||
this.ssHeadRef = null;
|
||||
|
||||
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
|
||||
this.sectionRefArr['selectedRef'] = null;
|
||||
this._getSlots = this._getSlots.bind(this);
|
||||
this._selectModule = this._selectModule.bind(this);
|
||||
this._getSectionMenu = this._getSectionMenu.bind(this);
|
||||
@@ -40,6 +44,8 @@ export default class SlotSection extends TranslatedComponent {
|
||||
this._drop = this._drop.bind(this);
|
||||
this._dragOverNone = this._dragOverNone.bind(this);
|
||||
this._close = this._close.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._handleSectionFocus = this._handleSectionFocus.bind(this);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
@@ -47,7 +53,59 @@ export default class SlotSection extends TranslatedComponent {
|
||||
// _getSlots()
|
||||
// _getSectionMenu()
|
||||
// _contextMenu()
|
||||
// componentDidUpdate(prevProps)
|
||||
|
||||
/**
|
||||
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
|
||||
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
|
||||
* ex: _keyDown(_keyDownfn, event)
|
||||
*
|
||||
* @param {SyntheticEvent} event KeyDown event
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
event.stopPropagation();
|
||||
if (event.currentTarget.nodeName === 'H1') {
|
||||
this._openMenu(this.sectionName, event);
|
||||
} else {
|
||||
event.currentTarget.click();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key == 'Tab') {
|
||||
if (event.shiftKey) {
|
||||
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
|
||||
event.preventDefault();
|
||||
this.sectionRefArr[this.lastRefId].focus();
|
||||
}
|
||||
} else {
|
||||
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
|
||||
event.preventDefault();
|
||||
this.sectionRefArr[this.firstRefId].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focus on appropriate Slot Section Menu element
|
||||
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
|
||||
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
|
||||
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
|
||||
*
|
||||
*/
|
||||
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
|
||||
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
|
||||
// set focus on the previously selected option for the currently open section menu
|
||||
this.sectionRefArr[this.selectedRefId].focus();
|
||||
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
|
||||
// set focus on the first option in the currently open section menu if none have been selected previously
|
||||
this.sectionRefArr[firstRef].focus();
|
||||
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
|
||||
// set focus on the section menu header when section menu is closed
|
||||
this.sectionRefArr['ssHeadRef'].focus();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Open a menu
|
||||
* @param {string} menu Menu name
|
||||
@@ -137,6 +195,15 @@ export default class SlotSection extends TranslatedComponent {
|
||||
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||
const mCopy = m.clone();
|
||||
this.props.ship.use(targetSlot, mCopy, false);
|
||||
let experimentalNum = this.props.ship.hardpoints
|
||||
.filter(s => s.m && s.m.experimental).length;
|
||||
// Remove the module on the last slot if we now exceed the number of
|
||||
// experimentals allowed
|
||||
if (m.experimental && 4 < experimentalNum) {
|
||||
this.props.ship.updateStats(originSlot, null, originSlot.m);
|
||||
originSlot.m = null; // Empty the slot
|
||||
originSlot.discountedCost = 0;
|
||||
}
|
||||
// Copy power info
|
||||
targetSlot.enabled = originSlot.enabled;
|
||||
targetSlot.priority = originSlot.priority;
|
||||
@@ -170,6 +237,18 @@ export default class SlotSection extends TranslatedComponent {
|
||||
targetSlot.priority = targetPriority;
|
||||
}
|
||||
this.props.onChange();
|
||||
this.props.ship
|
||||
.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +304,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
return (
|
||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
||||
<h1>{translate(this.sectionName)} <Equalizer/></h1>
|
||||
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
||||
</div>
|
||||
{this._getSlots()}
|
||||
|
||||
@@ -35,6 +35,21 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._modificationsSelected = false;
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this.modButton = null;
|
||||
this.slotDiv = null;
|
||||
}
|
||||
/**
|
||||
* Handle Enter key
|
||||
* @param {SyntheticEvent} event KeyDown event
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
if(event.target.className == 'r') {
|
||||
this._toggleModifications();
|
||||
}
|
||||
this.props.onOpen(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +63,13 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
let m = slot.m;
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
|
||||
validMods = [];
|
||||
}
|
||||
if (m && m.name && m.name === 'Guardian Power Distributor') {
|
||||
validMods = [];
|
||||
}
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
@@ -56,6 +77,9 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
modTT += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
@@ -79,22 +103,24 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
modButton = {this.modButton}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
|
||||
m={m}
|
||||
ship={ship}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
slotDiv = {this.slotDiv}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
|
||||
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
|
||||
<div>
|
||||
@@ -118,7 +144,7 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,12 +20,23 @@ export default class StandardSlotSection extends SlotSection {
|
||||
super(props, context, 'standard', 'core internal');
|
||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
||||
this._selectBulkhead = this._selectBulkhead.bind(this);
|
||||
this.selectedRefId = null;
|
||||
this.firstRefId = 'maxjump';
|
||||
this.lastRefId = 'racer';
|
||||
}
|
||||
/**
|
||||
* Handle focus if the component updates
|
||||
* @param {Object} prevProps React Component properties
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the lightest/optimal available standard modules
|
||||
*/
|
||||
_optimizeStandard() {
|
||||
this.selectedRefId = 'maxjump';
|
||||
this.props.ship.useLightestStandard();
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -39,6 +50,8 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
||||
*/
|
||||
_multiPurpose(shielded, bulkheadIndex) {
|
||||
this.selectedRefId = 'multipurpose';
|
||||
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
|
||||
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -51,6 +64,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
_optimizeCargo(shielded) {
|
||||
this.selectedRefId = 'trader';
|
||||
ShipRoles.trader(this.props.ship, shielded);
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -63,6 +77,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
_optimizeMiner(shielded) {
|
||||
this.selectedRefId = 'miner';
|
||||
ShipRoles.miner(this.props.ship, shielded);
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -75,6 +90,8 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
||||
*/
|
||||
_optimizeExplorer(planetary) {
|
||||
this.selectedRefId = 'explorer';
|
||||
if (planetary) this.selectedRefId = 'planetary';
|
||||
ShipRoles.explorer(this.props.ship, planetary);
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -86,6 +103,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* Racer role
|
||||
*/
|
||||
_optimizeRacer() {
|
||||
this.selectedRefId = 'racer';
|
||||
ShipRoles.racer(this.props.ship);
|
||||
this.props.onChange();
|
||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
||||
@@ -229,17 +247,17 @@ export default class StandardSlotSection extends SlotSection {
|
||||
let planetaryDisabled = this.props.ship.internal.length < 4;
|
||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('roles')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
|
||||
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
|
||||
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
|
||||
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
|
||||
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
|
||||
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</li>
|
||||
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
|
||||
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,97 @@ export class LinkIcon extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link / Permalink / Chain
|
||||
*/
|
||||
export class OrbisIcon extends SvgIcon {
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return (
|
||||
<g transform="scale(.037296)">
|
||||
<path d="m429 319c60.75 0 110 49.248 110 110 0 60.75-49.247 110-110 110s-110-49.247-110-110c0-60.749 49.248-110 110-110m0-20c-34.724 0-67.369 13.522-91.922 38.075s-38.075 57.198-38.075 91.922 13.522 67.369 38.075 91.922c24.553 24.554 57.198 38.075 91.922 38.075s67.369-13.521 91.922-38.075c24.554-24.553 38.075-57.198 38.075-91.922s-13.521-67.369-38.075-91.922c-24.553-24.553-57.198-38.075-91.922-38.075z" />
|
||||
<path d="m429 235c107.14 0 194 86.855 194 194s-86.855 194-194 194-194-86.855-194-194 86.855-194 194-194m0-20c-28.881 0-56.908 5.661-83.304 16.825-25.485 10.779-48.368 26.207-68.016 45.853-19.646 19.647-35.074 42.53-45.853 68.015-11.163 26.396-16.824 54.423-16.824 83.304s5.661 56.908 16.825 83.304c10.779 25.484 26.207 48.368 45.853 68.016 19.647 19.646 42.53 35.073 68.015 45.854 26.396 11.164 54.423 16.825 83.304 16.825s56.908-5.661 83.304-16.825c25.484-10.779 48.368-26.206 68.016-45.854 19.646-19.646 35.073-42.53 45.854-68.016 11.164-26.396 16.825-54.423 16.825-83.304s-5.661-56.908-16.825-83.304c-10.779-25.485-26.206-48.368-45.854-68.015-19.646-19.646-42.53-35.074-68.016-45.853-26.396-11.164-54.423-16.825-83.304-16.825z" />
|
||||
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z"/>
|
||||
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z" />
|
||||
<path d="m429 20c225.88 0 409 183.11 409 409s-183.11 409-409 409-409-183.11-409-409 183.11-409 409-409m0-20c-57.905 0-114.09 11.345-166.99 33.721-51.087 21.608-96.963 52.538-136.36 91.93s-70.321 85.269-91.93 136.36c-22.376 52.902-33.721 109.09-33.721 166.99s11.345 114.09 33.721 166.99c21.608 51.087 52.538 96.964 91.93 136.35 39.392 39.392 85.269 70.321 136.36 91.931 52.902 22.375 109.09 33.721 166.99 33.721s114.09-11.346 166.99-33.721c51.087-21.608 96.964-52.538 136.35-91.931 39.392-39.392 70.321-85.269 91.931-136.35 22.375-52.902 33.721-109.09 33.721-166.99s-11.346-114.09-33.721-166.99c-21.608-51.087-52.538-96.963-91.931-136.36-39.392-39.392-85.269-70.321-136.35-91.93-52.902-22.376-109.09-33.721-166.99-33.721z"/>
|
||||
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
|
||||
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
|
||||
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Material
|
||||
*/
|
||||
export class MatIcon extends SvgIcon {
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return<g xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#FF7100" d="M 24.86,4.18
|
||||
C 24.86,4.18 17.17,7.82 17.17,7.82
|
||||
17.17,7.82 15.35,14.55 15.35,14.55
|
||||
15.35,14.55 24.70,9.75 24.70,9.75
|
||||
24.70,9.75 24.86,4.18 24.86,4.18 Z
|
||||
M 32.21,17.45
|
||||
C 32.21,17.45 26.41,11.18 26.41,11.18
|
||||
26.41,11.18 19.51,11.51 19.51,11.51
|
||||
19.51,11.51 26.92,19.01 26.92,19.01
|
||||
26.92,19.01 32.21,17.45 32.21,17.45 Z
|
||||
M 21.99,28.62
|
||||
C 21.99,28.62 26.10,21.10 26.10,21.10
|
||||
26.10,21.10 23.66,14.57 23.66,14.57
|
||||
23.66,14.57 18.89,24.01 18.89,24.01
|
||||
18.89,24.01 21.99,28.62 21.99,28.62 Z
|
||||
M 8.33,22.24
|
||||
C 8.33,22.24 16.67,23.87 16.67,23.87
|
||||
16.67,23.87 22.06,19.51 22.06,19.51
|
||||
22.06,19.51 11.70,17.84 11.70,17.84
|
||||
11.70,17.84 8.33,22.24 8.33,22.24 Z
|
||||
M 10.11,7.14
|
||||
C 10.11,7.14 11.15,15.66 11.15,15.66
|
||||
11.15,15.66 16.92,19.49 16.92,19.49
|
||||
16.92,19.49 15.29,9.02 15.29,9.02
|
||||
15.29,9.02 10.11,7.14 10.11,7.14 Z
|
||||
M 27.69,2.67
|
||||
C 27.69,2.67 35.89,16.00 35.89,16.00
|
||||
35.89,16.00 27.69,29.33 27.69,29.33
|
||||
27.69,29.33 11.31,29.33 11.31,29.33
|
||||
11.31,29.33 3.11,16.00 3.11,16.00
|
||||
3.11,16.00 11.31,2.67 11.31,2.67
|
||||
11.31,2.67 27.67,2.67 27.67,2.67M 29.16,0.00
|
||||
C 29.16,0.00 27.69,0.00 27.69,0.00
|
||||
27.69,0.00 11.31,0.00 11.31,0.00
|
||||
11.31,0.00 9.84,0.00 9.84,0.00
|
||||
9.84,0.00 9.06,1.25 9.06,1.25
|
||||
9.06,1.25 0.87,14.57 0.87,14.57
|
||||
0.87,14.57 0.00,15.98 0.00,15.98
|
||||
0.00,15.98 0.87,17.39 0.87,17.39
|
||||
0.87,17.39 9.06,30.73 9.06,30.73
|
||||
9.06,30.73 9.84,32.00 9.84,32.00
|
||||
9.84,32.00 11.31,32.00 11.31,32.00
|
||||
11.31,32.00 27.69,32.00 27.69,32.00
|
||||
27.69,32.00 29.16,32.00 29.16,32.00
|
||||
29.16,32.00 29.94,30.73 29.94,30.73
|
||||
29.94,30.73 38.13,17.39 38.13,17.39
|
||||
38.13,17.39 39.00,15.98 39.00,15.98
|
||||
39.00,15.98 38.13,14.57 38.13,14.57
|
||||
38.13,14.57 29.94,1.25 29.94,1.25
|
||||
29.94,1.25 29.16,0.00 29.16,0.00
|
||||
29.16,0.00 29.16,0.00 29.16,0.00 Z" />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shopping icon (dollar sign)
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
@@ -8,7 +7,6 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
* Utility Slot Section
|
||||
*/
|
||||
export default class UtilitySlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -17,12 +15,23 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
constructor(props, context) {
|
||||
super(props, context, 'utility', 'utility mounts');
|
||||
this._empty = this._empty.bind(this);
|
||||
this.selectedRefId = null;
|
||||
this.firstRefId = 'emptyall';
|
||||
this.lastRefId = 'po';
|
||||
}
|
||||
/**
|
||||
* Handle focus if the component updates
|
||||
* @param {Object} prevProps React Component properties
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all utility slots and close the menu
|
||||
*/
|
||||
_empty() {
|
||||
this.selectedRefId = this.firstRefId;
|
||||
this.props.ship.emptyUtility();
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
@@ -36,6 +45,9 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
* @param {Synthetic} event Event
|
||||
*/
|
||||
_use(group, rating, name, event) {
|
||||
this.selectedRefId = group;
|
||||
if (rating !== null) this.selectedRefId += '-' + rating;
|
||||
|
||||
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
@@ -94,30 +106,29 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
|
||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||
<ul>
|
||||
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('sb')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('hs')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('ch')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('po')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +1,80 @@
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import React, { PropTypes } from 'react';
|
||||
import Measure from 'react-measure';
|
||||
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
const AXIS_COLOUR = '#C06400';
|
||||
|
||||
const ASPECT = 1;
|
||||
|
||||
const merge = function(one, two) {
|
||||
return Object.assign({}, one, two);
|
||||
};
|
||||
|
||||
/**
|
||||
* A vertical bar chart
|
||||
*/
|
||||
export default class VerticalBarChart extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired,
|
||||
yMax : PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._termtip = this._termtip.bind(this);
|
||||
|
||||
this.state = {
|
||||
dimensions: {
|
||||
width: 300,
|
||||
height: 300
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the bar chart
|
||||
* @returns {Object} the markup
|
||||
*/
|
||||
render() {
|
||||
const { width, height } = this.state.dimensions;
|
||||
const { tooltip, termtip } = this.context;
|
||||
|
||||
// Calculate maximum for Y
|
||||
let dataMax = Math.max(...this.props.data.map(d => d.value));
|
||||
if (dataMax == -Infinity) dataMax = 0;
|
||||
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
|
||||
const localMax = Math.max(dataMax, yMax);
|
||||
|
||||
return (
|
||||
<Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
|
||||
<div width='100%'>
|
||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
|
||||
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
|
||||
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
|
||||
</BarChart>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a term tip
|
||||
* @param {Object} d the data
|
||||
* @param {number} i the index
|
||||
* @param {Object} e the event
|
||||
* @returns {Object} termtip markup
|
||||
*/
|
||||
_termtip(d, i, e) {
|
||||
if (this.props.data[i].tooltip) {
|
||||
return this.context.termtip(this.props.data[i].tooltip, e);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A label that displays the value within the bar of the chart
|
||||
*/
|
||||
class ValueLabel extends React.Component {
|
||||
static propTypes = {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
value: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { x, y, payload, value } = this.props;
|
||||
|
||||
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
|
||||
|
||||
return (
|
||||
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
|
||||
);
|
||||
}
|
||||
};
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import React, { PropTypes } from 'react';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
const AXIS_COLOUR = '#C06400';
|
||||
|
||||
const ASPECT = 1;
|
||||
|
||||
const merge = function(one, two) {
|
||||
return Object.assign({}, one, two);
|
||||
};
|
||||
|
||||
/**
|
||||
* A vertical bar chart
|
||||
*/
|
||||
export default class VerticalBarChart extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired,
|
||||
yMax : PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._termtip = this._termtip.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the bar chart
|
||||
* @returns {Object} the markup
|
||||
*/
|
||||
render() {
|
||||
const { tooltip, termtip } = this.context;
|
||||
|
||||
// Calculate maximum for Y
|
||||
let dataMax = Math.max(...this.props.data.map(d => d.value));
|
||||
if (dataMax == -Infinity) dataMax = 0;
|
||||
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
|
||||
const localMax = Math.max(dataMax, yMax);
|
||||
|
||||
return (
|
||||
<ContainerDimensions>
|
||||
{ ({ width }) => (
|
||||
<div width='100%'>
|
||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
|
||||
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
|
||||
<Bar dataKey='value' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
|
||||
<LabelList dataKey='value' position='insideTop'/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</div>
|
||||
)}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a term tip
|
||||
* @param {Object} d the data
|
||||
* @param {number} i the index
|
||||
* @param {Object} e the event
|
||||
* @returns {Object} termtip markup
|
||||
*/
|
||||
_termtip(d, i, e) {
|
||||
if (this.props.data[i].tooltip) {
|
||||
return this.context.termtip(this.props.data[i].tooltip, e);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import Module from '../shipyard/Module';
|
||||
|
||||
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
||||
|
||||
@@ -74,7 +69,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
* Calculate the maximum range of a ship's weapons
|
||||
* @param {Object} ship The ship
|
||||
* @returns {int} The maximum range, in metres
|
||||
*/
|
||||
*/
|
||||
_calcMaxRange(ship) {
|
||||
let maxRange = 1000; // Minimum
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
@@ -184,7 +179,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<div>
|
||||
<LineChart
|
||||
xMax={maxRange}
|
||||
yMax={this.state.maxDps}
|
||||
@@ -198,7 +193,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as IT from './it';
|
||||
import * as RU from './ru';
|
||||
import * as PL from './pl';
|
||||
import * as PT from './pt';
|
||||
import * as CN from './cn';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
let fallbackTerms = EN.terms;
|
||||
@@ -27,6 +28,7 @@ export function getLanguage(langCode) {
|
||||
case 'ru': lang = RU; break;
|
||||
case 'pl': lang = PL; break;
|
||||
case 'pt': lang = PT; break;
|
||||
case 'cn': lang = CN; break;
|
||||
default:
|
||||
lang = EN;
|
||||
}
|
||||
@@ -60,17 +62,20 @@ export function getLanguage(langCode) {
|
||||
},
|
||||
translate,
|
||||
units: {
|
||||
ang: '°', // Angle
|
||||
CR: <u>{translate('CR')}</u>, // Credits
|
||||
kg: <u>{translate('kg')}</u>, // Kilograms
|
||||
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
|
||||
km: <u>{translate('km')}</u>, // Kilometers
|
||||
Ls: <u>{translate('Ls')}</u>, // Light Seconds
|
||||
LY: <u>{translate('LY')}</u>, // Light Years
|
||||
m: <u>{translate('m')}</u>, // Meters
|
||||
MJ: <u>{translate('MJ')}</u>, // Mega Joules
|
||||
'm/s': <u>{translate('m/s')}</u>, // Meters per second
|
||||
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
|
||||
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||
mps: <u>{translate('m/s')}</u>, // Metres per second
|
||||
pct: '%', // Percent
|
||||
ps: <u>{translate('/s')}</u>, // per second
|
||||
pm: <u>{translate('/min')}</u>, // per minute
|
||||
s: <u>{translate('secs')}</u>, // Seconds
|
||||
@@ -91,5 +96,6 @@ export const Languages = {
|
||||
fr: 'Français',
|
||||
ru: 'ру́сский',
|
||||
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",
|
||||
"Enter Name": "Entrer nom",
|
||||
"Explorer": "explorateur",
|
||||
"fastest range": "gamme la plus rapide",
|
||||
"farthest range": "gamme la plus rapide",
|
||||
"fuel": "carburant",
|
||||
"fuel level": "niveau de carburant",
|
||||
"full tank": "Réservoir plein",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,15 +16,18 @@
|
||||
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
|
||||
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
|
||||
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_FIFTY": "50% значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
|
||||
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
|
||||
"PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект",
|
||||
"PHRASE_BLUEPRINT_RESET": "Сбросить все модификаторы и чертеж",
|
||||
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
|
||||
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
||||
"PHRASE_SHOPPING_LIST": "Станции что продают эту сборку",
|
||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули",
|
||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители",
|
||||
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
|
||||
"PHRASE_SHOPPING_MATS": "Материалы которые нужны для сборки",
|
||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
|
||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
|
||||
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
||||
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
||||
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
||||
@@ -36,19 +39,21 @@
|
||||
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
|
||||
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
|
||||
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
|
||||
"PHRASE_OVERALL_DAMAGE": "Разбивка источников устойчивого ДПС",
|
||||
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
|
||||
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
|
||||
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
|
||||
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Щелкните правой кновкой мыши чтобы объединить в группу.",
|
||||
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
|
||||
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
||||
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
|
||||
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
|
||||
"TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
||||
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
||||
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||
"TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона",
|
||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
|
||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
||||
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
||||
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||
@@ -56,10 +61,12 @@
|
||||
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
|
||||
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
|
||||
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||
"TT_SUMMARY_BOOST_INTERVAL": "Время заполнения с 4 пунктами в СИС",
|
||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
||||
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
||||
"TT_SUMMARY_SHIELDS_SCB": "Прочность щита, включая бустеры и SCB",
|
||||
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
|
||||
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
|
||||
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
|
||||
@@ -68,17 +75,20 @@
|
||||
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
||||
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
|
||||
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
|
||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок",
|
||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
|
||||
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
||||
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
||||
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||
"HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||
"PHRASE_FAIL_EDENGINEER": "Не удалось отправить в EDEngineer (запустите EDEngineer и убедитесь, что API запущен, затем обновите страницу).",
|
||||
"PHRASE_FIREFOX_EDENGINEER": "Отправка в EDEngineer несовместима с настройками безопасности Firefox. Пожалуйста, попробуйте еще раз в Google Chrome.",
|
||||
"am": "Блок Автом. Полевого Ремонта",
|
||||
"bh": "Переборки",
|
||||
"bl": "Пучковый Лазер",
|
||||
"bsg": "Двухпоточный Щитогенератор",
|
||||
"bl": "Пучковый лазер",
|
||||
"bsg": "Двухпоточный щитогенератор",
|
||||
"c": "Орудие",
|
||||
"causres": "Caustic resistance",
|
||||
"cc": "Контроллер магнитного снаряда для сбора",
|
||||
"ch": "Разбрасыватель дипольных отражателей",
|
||||
"cr": "Грузовой стеллаж",
|
||||
@@ -89,7 +99,7 @@
|
||||
"fh": "Ангар для истребителя",
|
||||
"fi": "FSD-перехватчик",
|
||||
"fs": "Топливозаборник",
|
||||
"fsd": "Рамочно Сместительный двигатель",
|
||||
"fsd": "Рамочно-сместительный двигатель",
|
||||
"ft": "Топливный бак",
|
||||
"fx": "Контроллер магнитного снаряда для топлива",
|
||||
"hb": "Контроллер магнитного снаряда для взлома трюма",
|
||||
@@ -98,44 +108,74 @@
|
||||
"kw": "Сканер преступников",
|
||||
"ls": "Система жизнеобеспечения",
|
||||
"mc": "Многоствольное орудие",
|
||||
"axmc": "Многоствольное орудие АИ",
|
||||
"ml": "Проходочный лазер",
|
||||
"mr": "Ракетный лоток",
|
||||
"axmr": "Блок ракет АИ",
|
||||
"mrp": "Набор для усиления модуля",
|
||||
"nl": "Мины",
|
||||
"pa": "Ускоритель плазмы",
|
||||
"pas": "Комплект для сближения с планетой",
|
||||
"pc": "Контроллер магнитного снаряда для геологоразведки",
|
||||
"pce": "Каюта пассажира эконом-класса",
|
||||
"passenger capacity": "количество пассажиров",
|
||||
"pci": "Каюта пассажира бизнес-класса",
|
||||
"pcm": "Каюта пассажира первого класса",
|
||||
"pcq": "Каюта пассажира класса люкс",
|
||||
"pd": "Распределитель питания",
|
||||
"pl": "Ипмульсный лазер",
|
||||
"pl": "Импульсный лазер",
|
||||
"po": "Точечная оборона",
|
||||
"pp": "Силовая установка",
|
||||
"gpp": "Силовая установка Cтражей",
|
||||
"gpd": "Гибридный распределитель питания Стражей",
|
||||
"gpc": "Плазменная пушка Стражей",
|
||||
"ggc": "Пушка Гаусса Стражей",
|
||||
"gsrp": "Набор для усиления щита Стражей",
|
||||
"gfsb": "Ускоритель FSD Стражей",
|
||||
"ghrp": "Набор для усиления корпуса Стражей",
|
||||
"gmrp": "Набор для усиления модуля Стражей",
|
||||
"pwa": "Анализатор импульсных волн",
|
||||
"abl": "Абразивный бластер",
|
||||
"scl": "Пусковая установка для сейсмических снарядов",
|
||||
"sdm": "Вытесняющая ракета для добычи глубинных залежей",
|
||||
"tbsc": "Шоковое орудие",
|
||||
"gsc": "Осколочное орудие Стражей",
|
||||
"psg": "Призматический щитогенератор",
|
||||
"pv": "Гараж для планетарного транспорта",
|
||||
"rf": "Устройство переработки",
|
||||
"rfl": "Зенитная установка (снаряды с дистанционным подрывом)",
|
||||
"rg": "Электромагнитная пушка",
|
||||
"rsl": "Дроны-исследователи",
|
||||
"s": "Сенсоры",
|
||||
"sb": "Усилитель щита",
|
||||
"sc": "Сканер обнаружения",
|
||||
"scb": "Щитонакопитель",
|
||||
"sfn": "Нейтрализатор глушащего поля",
|
||||
"sg": "Щитогенератор",
|
||||
"ss": "Сканер Поверхностей",
|
||||
"ss": "Сканер поверхностей",
|
||||
"sua": "Помощь в гиперкрейсерском режиме",
|
||||
"t": "Маневровые двигатели",
|
||||
"tp": "Торпедная стойка",
|
||||
"ul": "Пульсирующие лазеры",
|
||||
"Send To EDEngineer": "Отправить в EDEngineer",
|
||||
"ws": "Сканер следа FSD",
|
||||
"rpl": "Дроны-ремонтники",
|
||||
"rcpl": "ДРоны-разведчики",
|
||||
"xs": "Сканер «инопланетянин»",
|
||||
"tbem": "Блок энзимных ракет",
|
||||
"tbrfl": "Установка для стрельбы стреловидными снарядами с дистанционным подрывом",
|
||||
"dtl": "Дроны-очистители",
|
||||
"mahr": "Набор для усиления корпуса из Метасплава",
|
||||
"emptyrestricted": "пусто (ограниченно)",
|
||||
"damage dealt to": "Урон нанесён",
|
||||
"damage received from": "Урон получен от",
|
||||
"against shields": "Против шитов",
|
||||
"against shields": "Против щитов",
|
||||
"against hull": "Против корпуса",
|
||||
"total effective shield": "Общие эффективные щиты",
|
||||
"ammunition": "Припасы",
|
||||
"secs": "с",
|
||||
"rebuildsperbay": "Построек за полосу",
|
||||
"mroll": "Roll",
|
||||
"worst": "Худшее",
|
||||
"average": "Среднее",
|
||||
"random": "Случайное",
|
||||
@@ -148,8 +188,9 @@
|
||||
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
|
||||
"eps": "Энергия в секунду",
|
||||
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
||||
"fallofffromrange": "Спад",
|
||||
"hps": "Нагрев в секунду",
|
||||
"hpsshps": "Heat per second (sustained heat per second)",
|
||||
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
|
||||
"damage by": "Урон",
|
||||
"damage from": "Урон от",
|
||||
"shield cells": "Щитонакопители",
|
||||
@@ -164,13 +205,15 @@
|
||||
"internal protection": "Внутренняя защита",
|
||||
"external protection": "Внешняя защита",
|
||||
"engagement range": "Боевое расстояние",
|
||||
"boost interval": "Интервал повышения",
|
||||
"total": "Всего",
|
||||
"ammo": "Боекомплект",
|
||||
"ammo": "Макс. боекомплект",
|
||||
"boot": "Время загрузки",
|
||||
"hacktime": "Время взлома",
|
||||
"brokenregen": "Скорость восстановления при пробое",
|
||||
"burst": "Длина очереди",
|
||||
"burstrof": "Скорострельность очереди",
|
||||
"clip": "Боекомплект",
|
||||
"clip": "Размер боекомплекта",
|
||||
"damage": "Урон",
|
||||
"distdraw": "Тяга распределителя",
|
||||
"duration": "Продолжительность",
|
||||
@@ -183,7 +226,7 @@
|
||||
"hullreinforcement": "Укрепление корпуса",
|
||||
"integrity": "Целостность",
|
||||
"jitter": "Дрожание",
|
||||
"kinres": "Сопротивление китетическому урону",
|
||||
"kinres": "Сопротивление кинетическому урону",
|
||||
"maxfuel": "Макс. топлива на прыжок",
|
||||
"mass": "Масса",
|
||||
"optmass": "Оптимизированная масса",
|
||||
@@ -199,11 +242,14 @@
|
||||
"rof": "Скорострельность",
|
||||
"angle": "Угол сканера",
|
||||
"scanrate": "Скорость сканера",
|
||||
"proberadius": "Радиус зонда",
|
||||
"scantime": "Время сканирования",
|
||||
"shield": "Щит",
|
||||
"armour": "Броня",
|
||||
"shieldboost": "Усиление щитов",
|
||||
"shieldreinforcement": "Усилитель щита",
|
||||
"shotspeed": "Скорость выстрела",
|
||||
"shotdmg": "Урон за выстрел(DPS)",
|
||||
"spinup": "Время раскрутки",
|
||||
"syscap": "Ресурс систем",
|
||||
"sysrate": "Перезарядка систем",
|
||||
@@ -234,9 +280,12 @@
|
||||
"explosive": "Взрывч.",
|
||||
"kinetic": "Механич.",
|
||||
"thermal": "Тепл.",
|
||||
"caustic": "Каустик",
|
||||
"generator": "Генератор",
|
||||
"boosters": "Усилители",
|
||||
"cells": "Ячейки",
|
||||
"shield addition": "ДОбавления к щиту",
|
||||
"jump addition": "ДОбавления к прыжку",
|
||||
"bulkheads": "Переборки",
|
||||
"reinforcement": "Усилители",
|
||||
"power and costs": "Энергия и стоимость",
|
||||
@@ -269,13 +318,45 @@
|
||||
"opponent": "Противник",
|
||||
"opponent's shields": "Щит противника",
|
||||
"opponent's armour": "Броня противника",
|
||||
"overall damage": "overall damage",
|
||||
"shield damage sources": "источники урона по щиту",
|
||||
"armour damage sources": "источники урона по броне",
|
||||
"never": "Никогда",
|
||||
"stock": "базовый",
|
||||
"boost": "форсаж",
|
||||
"/s": "/с",
|
||||
"m/s": "м/с",
|
||||
"tab_defence": "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": "Минимальный ранг империи для покупки",
|
||||
"\/s": "\/с",
|
||||
"m\/s": "м\/с",
|
||||
"Ls": "Св.сек",
|
||||
"LY": "Св.лет",
|
||||
"CR": "кр.",
|
||||
@@ -299,7 +380,7 @@
|
||||
"edit data": "Редактирование",
|
||||
"empty all": "пусто все",
|
||||
"Enter Name": "Введите имя",
|
||||
"fastest range": "быстрый диапазон",
|
||||
"farthest range": "быстрый диапазон",
|
||||
"fuel level": "уровень топлива",
|
||||
"full tank": "Полный бак",
|
||||
"internal compartments": "внутренние отсеки",
|
||||
@@ -315,7 +396,6 @@
|
||||
"about": "О ...",
|
||||
"action": "Действие",
|
||||
"added": "Добавлено",
|
||||
"armour": "Броня",
|
||||
"available": "доступно",
|
||||
"backup": "Резервная копия",
|
||||
"bins": "контейнеры",
|
||||
@@ -363,7 +443,7 @@
|
||||
"repair": "Починка",
|
||||
"ret": "Убр.",
|
||||
"retracted": "Убрано",
|
||||
"ROF": "В/сек",
|
||||
"ROF": "В\/сек",
|
||||
"save": "Сохранить",
|
||||
"sell": "Продать",
|
||||
"settings": "Настройки",
|
||||
@@ -381,4 +461,4 @@
|
||||
"URL": "Ссылка",
|
||||
"WEP": "ОРУЖ",
|
||||
"yes": "Да"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'babel-polyfill';
|
||||
import '@babel/polyfill';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import '../less/app.less';
|
||||
|
||||
@@ -6,7 +6,6 @@ import { CoriolisLogo, GitHub } from '../components/SvgIcons';
|
||||
* About Page
|
||||
*/
|
||||
export default class AboutPage extends Page {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -23,24 +22,79 @@ export default class AboutPage extends Page {
|
||||
* @return {React.Component} The page contents
|
||||
*/
|
||||
renderPage() {
|
||||
return <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>
|
||||
return (
|
||||
<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>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>
|
||||
<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>
|
||||
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'>
|
||||
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
|
||||
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
|
||||
github.com/EDCD/coriolis
|
||||
</a>
|
||||
<a
|
||||
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>
|
||||
github.com/EDCD/coriolis
|
||||
</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>
|
||||
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
|
||||
</div>;
|
||||
<h3>Chat</h3>
|
||||
<p>
|
||||
You can chat to us on our{' '}
|
||||
<a href="https://discord.gg/0uwCh6R62aPRjk9w" target="_blank">
|
||||
EDCD Discord server
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,14 @@ 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 {
|
||||
FloppyDisk,
|
||||
Bin,
|
||||
Download,
|
||||
Embed,
|
||||
Rocket,
|
||||
LinkIcon
|
||||
} from '../components/SvgIcons';
|
||||
import ShortenUrl from '../utils/ShortenUrl';
|
||||
import { comparisonBBCode } from '../utils/BBCode';
|
||||
const browser = require('detect-browser');
|
||||
@@ -42,7 +49,6 @@ function sortBy(predicate) {
|
||||
* Comparison Page
|
||||
*/
|
||||
export default class ComparisonPage extends Page {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -81,7 +87,13 @@ export default class ComparisonPage extends Page {
|
||||
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]));
|
||||
builds.push(
|
||||
this._createBuild(
|
||||
shipId,
|
||||
buildName,
|
||||
allBuilds[shipId][buildName]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +101,9 @@ export default class ComparisonPage extends Page {
|
||||
let comparisonData = Persist.getComparison(name);
|
||||
if (comparisonData) {
|
||||
defaultFacets = comparisonData.facets;
|
||||
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
|
||||
comparisonData.builds.forEach(b =>
|
||||
builds.push(this._createBuild(b.shipId, b.buildName))
|
||||
);
|
||||
saved = true;
|
||||
newName = name;
|
||||
}
|
||||
@@ -101,7 +115,7 @@ export default class ComparisonPage extends Page {
|
||||
newName = name = comparisonData.n;
|
||||
predicate = comparisonData.p;
|
||||
desc = comparisonData.d;
|
||||
comparisonData.b.forEach((build) => {
|
||||
comparisonData.b.forEach(build => {
|
||||
builds.push(this._createBuild(build.s, build.n, build.c));
|
||||
if (!importObj[build.s]) {
|
||||
importObj[build.s] = {};
|
||||
@@ -118,9 +132,9 @@ export default class ComparisonPage extends Page {
|
||||
let selectedFacets = new Array(selectedLength);
|
||||
|
||||
for (let i = 0; i < ShipFacets.length; i++) {
|
||||
let facet = Object.assign({ }, ShipFacets[i]);
|
||||
let facet = Object.assign({}, ShipFacets[i]);
|
||||
let defaultIndex = defaultFacets.indexOf(facet.i);
|
||||
if(defaultIndex == -1) {
|
||||
if (defaultIndex == -1) {
|
||||
facets.push(facet);
|
||||
} else {
|
||||
facet.active = true;
|
||||
@@ -155,17 +169,18 @@ export default class ComparisonPage extends Page {
|
||||
_createBuild(id, name, code) {
|
||||
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
|
||||
|
||||
if (!code) { // No build found
|
||||
if (!code) {
|
||||
// No build found
|
||||
return;
|
||||
}
|
||||
|
||||
let data = Ships[id]; // Get ship properties
|
||||
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.buildFrom(code); // Populate components from code
|
||||
b.buildName = name;
|
||||
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
||||
return b;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state with the specified sort predicates
|
||||
@@ -184,13 +199,18 @@ export default class ComparisonPage extends Page {
|
||||
}
|
||||
|
||||
this.setState({ predicate, desc });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show selected builds modal
|
||||
*/
|
||||
_selectBuilds() {
|
||||
this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
|
||||
this.context.showModal(
|
||||
<ModalCompare
|
||||
onSelect={this._buildsSelected}
|
||||
builds={this.state.builds}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +244,7 @@ export default class ComparisonPage extends Page {
|
||||
_facetDrag(e) {
|
||||
this.nodeAfter = false;
|
||||
this.dragged = e.currentTarget;
|
||||
let placeholder = this.placeholder = document.createElement('li');
|
||||
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')) {
|
||||
@@ -262,7 +282,7 @@ export default class ComparisonPage extends Page {
|
||||
_facetDragOver(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if(e.target.className == 'facet-placeholder') {
|
||||
if (e.target.className == 'facet-placeholder') {
|
||||
return;
|
||||
} else if (e.target != e.currentTarget) {
|
||||
this.over = e.target;
|
||||
@@ -272,7 +292,7 @@ export default class ComparisonPage extends Page {
|
||||
let parent = e.target.parentNode;
|
||||
|
||||
if (parent == e.currentTarget) {
|
||||
if(relX > width && this.dragged != e.target) {
|
||||
if (relX > width && this.dragged != e.target) {
|
||||
this.nodeAfter = true;
|
||||
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
|
||||
} else {
|
||||
@@ -321,7 +341,7 @@ export default class ComparisonPage extends Page {
|
||||
let { newName, builds, facets } = this.state;
|
||||
let selectedFacets = [];
|
||||
|
||||
facets.forEach((f) => {
|
||||
facets.forEach(f => {
|
||||
if (f.active) {
|
||||
selectedFacets.unshift(f.i);
|
||||
}
|
||||
@@ -348,14 +368,20 @@ export default class ComparisonPage extends Page {
|
||||
|
||||
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
|
||||
let loc = window.location;
|
||||
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
|
||||
return (
|
||||
loc.protocol +
|
||||
'//' +
|
||||
loc.host +
|
||||
'/comparison?code=' +
|
||||
encodeURIComponent(code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the long permalink URL
|
||||
*/
|
||||
_genPermalink() {
|
||||
this.context.showModal(<ModalPermalink url={this._buildUrl()}/>);
|
||||
this.context.showModal(<ModalPermalink url={this._buildUrl()} />);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,18 +391,25 @@ export default class ComparisonPage extends Page {
|
||||
let { translate, formats } = this.context.language;
|
||||
let { facets, builds } = this.state;
|
||||
|
||||
let generator = (callback) => {
|
||||
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))
|
||||
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}
|
||||
/>);
|
||||
this.context.showModal(
|
||||
<ModalExport
|
||||
title={translate('forum') + ' BBCode'}
|
||||
generator={generator}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,7 +442,8 @@ export default class ComparisonPage extends Page {
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
||||
if (this.context.route !== nextContext.route) {
|
||||
// Only reinit state if the route has changed
|
||||
this.setState(this._initState(nextContext));
|
||||
}
|
||||
}
|
||||
@@ -419,7 +453,10 @@ export default class ComparisonPage extends Page {
|
||||
*/
|
||||
componentWillMount() {
|
||||
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
|
||||
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
|
||||
this.persistListener = Persist.addListener(
|
||||
'discounts',
|
||||
this._updateDiscounts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -444,65 +481,132 @@ export default class ComparisonPage extends Page {
|
||||
renderPage() {
|
||||
let translate = this.context.language.translate;
|
||||
let compareHeader;
|
||||
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
|
||||
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>;
|
||||
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>;
|
||||
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'>
|
||||
<div
|
||||
className={'page'}
|
||||
style={{ fontSize: this.context.sizeRatio + 'em' }}
|
||||
>
|
||||
<table id="comparison">
|
||||
<tbody>
|
||||
{compareHeader}
|
||||
<tr key='facets'>
|
||||
<td className='head'>{translate('compare')}</td>
|
||||
<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)}>
|
||||
<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} />
|
||||
<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>
|
||||
{!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}
|
||||
@@ -515,8 +619,8 @@ export default class ComparisonPage extends Page {
|
||||
desc={desc}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Page from './Page';
|
||||
* 404 Page
|
||||
*/
|
||||
export default class NotFoundPage extends Page {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -22,6 +21,10 @@ export default class NotFoundPage extends Page {
|
||||
* @return {React.Component} The page contents
|
||||
*/
|
||||
renderPage() {
|
||||
return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
|
||||
return (
|
||||
<div className="page" style={{ marginTop: 30 }}>
|
||||
Page <small>{this.context.route.path}</small> Not Found
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,19 @@ import Router from '../Router';
|
||||
import Persist from '../stores/Persist';
|
||||
import * as Utils from '../utils/UtilityFunctions';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import * as _ from 'lodash';
|
||||
import { toDetailedBuild } from '../shipyard/Serializer';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons';
|
||||
import {
|
||||
FloppyDisk,
|
||||
Bin,
|
||||
Switch,
|
||||
Download,
|
||||
Reload,
|
||||
LinkIcon,
|
||||
ShoppingIcon,
|
||||
MatIcon,
|
||||
} from '../components/SvgIcons';
|
||||
import LZString from 'lz-string';
|
||||
import ShipSummaryTable from '../components/ShipSummaryTable';
|
||||
import StandardSlotSection from '../components/StandardSlotSection';
|
||||
@@ -25,6 +35,7 @@ import EngagementRange from '../components/EngagementRange';
|
||||
import OutfittingSubpages from '../components/OutfittingSubpages';
|
||||
import ModalExport from '../components/ModalExport';
|
||||
import ModalPermalink from '../components/ModalPermalink';
|
||||
import ModalShoppingList from '../components/ModalShoppingList';
|
||||
|
||||
/**
|
||||
* Document Title Generator
|
||||
@@ -40,7 +51,6 @@ function getTitle(shipName, buildName) {
|
||||
* The Outfitting Page
|
||||
*/
|
||||
export default class OutfittingPage extends Page {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -58,6 +68,7 @@ export default class OutfittingPage extends Page {
|
||||
this._fuelUpdated = this._fuelUpdated.bind(this);
|
||||
this._opponentUpdated = this._opponentUpdated.bind(this);
|
||||
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
|
||||
this._sectionMenuRefs = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,22 +82,38 @@ export default class OutfittingPage extends Page {
|
||||
let shipId = params.ship;
|
||||
let code = params.code;
|
||||
let buildName = params.bn;
|
||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
||||
let savedCode = Persist.getBuild(shipId, buildName);
|
||||
if (!data) {
|
||||
return { error: { message: 'Ship not found: ' + shipId } };
|
||||
}
|
||||
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
|
||||
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
|
||||
if (code) {
|
||||
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
|
||||
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
|
||||
} else {
|
||||
ship.buildWith(data.defaults); // Populate with default components
|
||||
ship.buildWith(data.defaults); // Populate with default components
|
||||
}
|
||||
|
||||
this._getTitle = getTitle.bind(this, data.properties.name);
|
||||
|
||||
// Obtain ship control from code
|
||||
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
|
||||
const {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep,
|
||||
engagementRange
|
||||
} = this._obtainControlFromCode(ship, code);
|
||||
return {
|
||||
error: null,
|
||||
title: this._getTitle(buildName),
|
||||
@@ -100,6 +127,9 @@ export default class OutfittingPage extends Page {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
@@ -122,7 +152,10 @@ export default class OutfittingPage extends Page {
|
||||
};
|
||||
|
||||
if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) {
|
||||
stateChanges.savedCode = Persist.getBuild(this.state.shipId, stateChanges.newBuildName);
|
||||
stateChanges.savedCode = Persist.getBuild(
|
||||
this.state.shipId,
|
||||
stateChanges.newBuildName
|
||||
);
|
||||
} else {
|
||||
stateChanges.savedCode = null;
|
||||
}
|
||||
@@ -148,7 +181,9 @@ export default class OutfittingPage extends Page {
|
||||
* @returns {string} the code for this ship
|
||||
*/
|
||||
_fullCode(ship, fuel, cargo) {
|
||||
return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
|
||||
return `${ship.toString()}.${LZString.compressToBase64(
|
||||
this._controlCode(fuel, cargo)
|
||||
)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,10 +197,17 @@ export default class OutfittingPage extends Page {
|
||||
let sys = 2;
|
||||
let eng = 2;
|
||||
let wep = 2;
|
||||
let mcSys = 0;
|
||||
let mcEng = 0;
|
||||
let mcWep = 0;
|
||||
let boost = false;
|
||||
let fuel = ship.fuelCapacity;
|
||||
let cargo = ship.cargoCapacity;
|
||||
let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
|
||||
let opponent = new Ship(
|
||||
'eagle',
|
||||
Ships['eagle'].properties,
|
||||
Ships['eagle'].slots
|
||||
).buildWith(Ships['eagle'].defaults);
|
||||
let opponentSys = 2;
|
||||
let opponentEng = 2;
|
||||
let opponentWep = 2;
|
||||
@@ -177,16 +219,25 @@ export default class OutfittingPage extends Page {
|
||||
const parts = code.split('.');
|
||||
if (parts.length >= 5) {
|
||||
// We have control information in the code
|
||||
const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
|
||||
const control = LZString.decompressFromBase64(
|
||||
Utils.fromUrlSafe(parts[4])
|
||||
).split('/');
|
||||
sys = parseFloat(control[0]);
|
||||
eng = parseFloat(control[1]);
|
||||
wep = parseFloat(control[2]);
|
||||
if (sys + eng + wep > 6) {
|
||||
sys = eng = wep = 2;
|
||||
}
|
||||
boost = control[3] == 1 ? true : false;
|
||||
fuel = parseFloat(control[4]);
|
||||
cargo = parseInt(control[5]);
|
||||
fuel = parseFloat(control[4]) || fuel;
|
||||
cargo = parseInt(control[5]) || cargo;
|
||||
if (control[6]) {
|
||||
const shipId = control[6];
|
||||
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
|
||||
opponent = new Ship(
|
||||
shipId,
|
||||
Ships[shipId].properties,
|
||||
Ships[shipId].slots
|
||||
);
|
||||
if (control[7] && Persist.getBuild(shipId, control[7])) {
|
||||
// Ship is a particular build
|
||||
const opponentCode = Persist.getBuild(shipId, control[7]);
|
||||
@@ -196,10 +247,12 @@ export default class OutfittingPage extends Page {
|
||||
// Obtain opponent's sys/eng/wep pips from their code
|
||||
const opponentParts = opponentCode.split('.');
|
||||
if (opponentParts.length >= 5) {
|
||||
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
|
||||
opponentSys = parseFloat(opponentControl[0]);
|
||||
opponentEng = parseFloat(opponentControl[1]);
|
||||
opponentWep = parseFloat(opponentControl[2]);
|
||||
const opponentControl = LZString.decompressFromBase64(
|
||||
Utils.fromUrlSafe(opponentParts[4])
|
||||
).split('/');
|
||||
opponentSys = parseFloat(opponentControl[0]) || opponentSys;
|
||||
opponentEng = parseFloat(opponentControl[1]) || opponentEng;
|
||||
opponentWep = parseFloat(opponentControl[2]) || opponentWep;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -207,21 +260,50 @@ export default class OutfittingPage extends Page {
|
||||
opponent.buildWith(Ships[shipId].defaults);
|
||||
}
|
||||
}
|
||||
engagementRange = parseInt(control[8]);
|
||||
engagementRange = parseInt(control[8]) || engagementRange;
|
||||
|
||||
// Multi-crew pips were introduced later on so assign default values
|
||||
// because those values might not be present.
|
||||
mcSys = parseInt(control[9]) || mcSys;
|
||||
mcEng = parseInt(control[10]) || mcEng;
|
||||
mcWep = parseInt(control[11]) || mcWep;
|
||||
}
|
||||
}
|
||||
|
||||
return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
|
||||
return {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep,
|
||||
engagementRange
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when pips have been updated
|
||||
* Triggered when pips have been updated. Multi-crew pips are already included
|
||||
* in sys, eng and wep but mcSys, mcEng and mcWep make clear where each pip
|
||||
* comes from.
|
||||
* @param {number} sys SYS pips
|
||||
* @param {number} eng ENG pips
|
||||
* @param {number} wep WEP pips
|
||||
* @param {number} mcSys SYS pips from multi-crew
|
||||
* @param {number} mcEng ENG pips from multi-crew
|
||||
* @param {number} mcWep WEP pips from multi-crew
|
||||
*/
|
||||
_pipsUpdated(sys, eng, wep) {
|
||||
this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange());
|
||||
_pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
|
||||
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () =>
|
||||
this._updateRouteOnControlChange()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +311,7 @@ export default class OutfittingPage extends Page {
|
||||
* @param {boolean} boost true if boosting
|
||||
*/
|
||||
_boostUpdated(boost) {
|
||||
this.setState({ boost }, () => this._updateRouteOnControlChange());
|
||||
this.setState({ boost }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,7 +319,7 @@ export default class OutfittingPage extends Page {
|
||||
* @param {number} fuel the amount of fuel, in T
|
||||
*/
|
||||
_fuelUpdated(fuel) {
|
||||
this.setState({ fuel }, () => this._updateRouteOnControlChange());
|
||||
this.setState({ fuel }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +327,7 @@ export default class OutfittingPage extends Page {
|
||||
* @param {number} cargo the amount of cargo, in T
|
||||
*/
|
||||
_cargoUpdated(cargo) {
|
||||
this.setState({ cargo }, () => this._updateRouteOnControlChange());
|
||||
this.setState({ cargo }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,7 +335,9 @@ export default class OutfittingPage extends Page {
|
||||
* @param {number} engagementRange the engagement range, in m
|
||||
*/
|
||||
_engagementRangeUpdated(engagementRange) {
|
||||
this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
|
||||
this.setState({ engagementRange }, () =>
|
||||
this._updateRouteOnControlChange()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,7 +346,11 @@ export default class OutfittingPage extends Page {
|
||||
* @param {string} opponentBuild the name of the opponent's build
|
||||
*/
|
||||
_opponentUpdated(opponent, opponentBuild) {
|
||||
const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
|
||||
const opponentShip = new Ship(
|
||||
opponent,
|
||||
Ships[opponent].properties,
|
||||
Ships[opponent].slots
|
||||
);
|
||||
let opponentSys = this.state.opponentSys;
|
||||
let opponentEng = this.state.opponentEng;
|
||||
let opponentWep = this.state.opponentWep;
|
||||
@@ -270,9 +358,13 @@ export default class OutfittingPage extends Page {
|
||||
// Ship is a particular build
|
||||
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
|
||||
// Set pips for opponent
|
||||
const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.');
|
||||
const opponentParts = Persist.getBuild(opponent, opponentBuild).split(
|
||||
'.'
|
||||
);
|
||||
if (opponentParts.length >= 5) {
|
||||
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
|
||||
const opponentControl = LZString.decompressFromBase64(
|
||||
Utils.fromUrlSafe(opponentParts[4])
|
||||
).split('/');
|
||||
opponentSys = parseFloat(opponentControl[0]);
|
||||
opponentEng = parseFloat(opponentControl[1]);
|
||||
opponentWep = parseFloat(opponentControl[2]);
|
||||
@@ -285,7 +377,16 @@ export default class OutfittingPage extends Page {
|
||||
opponentWep = 2;
|
||||
}
|
||||
|
||||
this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
|
||||
this.setState(
|
||||
{
|
||||
opponent: opponentShip,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep
|
||||
},
|
||||
() => this._updateRouteOnControlChange()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,8 +396,22 @@ export default class OutfittingPage extends Page {
|
||||
* @returns {string} The control code
|
||||
*/
|
||||
_controlCode(fuel, cargo) {
|
||||
const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state;
|
||||
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`;
|
||||
const {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
} = this.state;
|
||||
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel ||
|
||||
this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${
|
||||
opponentBuild ? opponentBuild : ''
|
||||
}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -307,27 +422,44 @@ export default class OutfittingPage extends Page {
|
||||
const { ship, buildName, newBuildName, shipId } = this.state;
|
||||
|
||||
// If this is a stock ship the code won't be set, so ensure that we have it
|
||||
const code = this.state.code || ship.toString();
|
||||
const code = this.state.code || ship.toString();
|
||||
|
||||
Persist.saveBuild(shipId, newBuildName, code);
|
||||
this._updateRoute(shipId, newBuildName, code);
|
||||
|
||||
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
|
||||
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
|
||||
if (
|
||||
shipId === this.state.opponent.id &&
|
||||
buildName === this.state.opponentBuild
|
||||
) {
|
||||
// This is a save of our current opponent build; update it
|
||||
opponentBuild = newBuildName;
|
||||
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
|
||||
opponent = new Ship(
|
||||
shipId,
|
||||
Ships[shipId].properties,
|
||||
Ships[shipId].slots
|
||||
).buildFrom(code);
|
||||
opponentSys = this.state.sys;
|
||||
opponentEng = this.state.eng;
|
||||
opponentWep = this.state.wep;
|
||||
} else {
|
||||
opponentBuild = this.state.opponentBuild;
|
||||
opponent = this.state.opponent;
|
||||
opponent = this.state.opponent;
|
||||
opponentSys = this.state.opponentSys;
|
||||
opponentEng = this.state.opponentEng;
|
||||
opponentWep = this.state.opponentWep;
|
||||
}
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
|
||||
this.setState({
|
||||
buildName: newBuildName,
|
||||
code,
|
||||
savedCode: code,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep,
|
||||
title: this._getTitle(newBuildName)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,7 +471,12 @@ export default class OutfittingPage extends Page {
|
||||
Persist.deleteBuild(shipId, buildName);
|
||||
Persist.saveBuild(shipId, newBuildName, code);
|
||||
this._updateRoute(shipId, newBuildName, code);
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
|
||||
this.setState({
|
||||
buildName: newBuildName,
|
||||
code,
|
||||
savedCode: code,
|
||||
opponentBuild: newBuildName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,19 +496,38 @@ export default class OutfittingPage extends Page {
|
||||
ship.buildWith(Ships[shipId].defaults);
|
||||
// Reset controls
|
||||
const code = ship.toString();
|
||||
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
|
||||
// Update state, and refresh the ship
|
||||
this.setState({
|
||||
const {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
}, () => this._updateRoute(shipId, buildName, code));
|
||||
} = this._obtainControlFromCode(ship, code);
|
||||
// Update state, and refresh the ship
|
||||
this.setState(
|
||||
{
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
},
|
||||
() => this._updateRoute(shipId, buildName, code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,7 +538,10 @@ export default class OutfittingPage extends Page {
|
||||
Persist.deleteBuild(shipId, buildName);
|
||||
|
||||
let opponentBuild;
|
||||
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
|
||||
if (
|
||||
shipId === this.state.opponent.id &&
|
||||
buildName === this.state.opponentBuild
|
||||
) {
|
||||
// Our current opponent has been deleted; revert to stock
|
||||
opponentBuild = null;
|
||||
} else {
|
||||
@@ -399,11 +558,13 @@ export default class OutfittingPage extends Page {
|
||||
_exportBuild() {
|
||||
let translate = this.context.language.translate;
|
||||
let { buildName, ship } = this.state;
|
||||
this.context.showModal(<ModalExport
|
||||
title={(buildName || ship.name) + ' ' + translate('export')}
|
||||
description={translate('PHRASE_EXPORT_DESC')}
|
||||
data={toDetailedBuild(buildName, ship, ship.toString())}
|
||||
/>);
|
||||
this.context.showModal(
|
||||
<ModalExport
|
||||
title={(buildName || ship.name) + ' ' + translate('export')}
|
||||
description={translate('PHRASE_EXPORT_DESC')}
|
||||
data={toDetailedBuild(buildName, ship, ship.toString())}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,19 +577,38 @@ export default class OutfittingPage extends Page {
|
||||
this.state.ship.buildFrom(code);
|
||||
|
||||
// Obtain controls from the code
|
||||
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
|
||||
// Update state, and refresh the route when complete
|
||||
this.setState({
|
||||
const {
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
}, () => this._updateRoute(shipId, buildName, code));
|
||||
} = this._obtainControlFromCode(ship, code);
|
||||
// Update state, and refresh the route when complete
|
||||
this.setState(
|
||||
{
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
},
|
||||
() => this._updateRoute(shipId, buildName, code)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -444,8 +624,14 @@ export default class OutfittingPage extends Page {
|
||||
}
|
||||
const code = this._fullCode(ship, fuel, cargo);
|
||||
// Only update the state if this really has been updated
|
||||
if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) {
|
||||
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
|
||||
if (
|
||||
this.state.code != code ||
|
||||
this.state.cargo != cargo ||
|
||||
this.state.fuel != fuel
|
||||
) {
|
||||
this.setState({ code, cargo, fuel }, () =>
|
||||
this._updateRoute(shipId, buildName, code)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +651,8 @@ export default class OutfittingPage extends Page {
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
||||
if (this.context.route !== nextContext.route) {
|
||||
// Only reinit state if the route has changed
|
||||
this.setState(this._initState(nextProps, nextContext));
|
||||
}
|
||||
}
|
||||
@@ -488,7 +675,7 @@ export default class OutfittingPage extends Page {
|
||||
* Generates the short URL
|
||||
*/
|
||||
_genShortlink() {
|
||||
this.context.showModal(<ModalPermalink url={window.location.href}/>);
|
||||
this.context.showModal(<ModalPermalink url={window.location.href} />);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,10 +686,23 @@ export default class OutfittingPage extends Page {
|
||||
|
||||
const shipId = Ships[ship.id].eddbID;
|
||||
// Provide unique list of non-PP module EDDB IDs
|
||||
const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i);
|
||||
const modIds = ship.internal
|
||||
.concat(ship.bulkheads, ship.standard, ship.hardpoints)
|
||||
.filter(slot => slot !== null && slot.m !== null && !slot.m.pp)
|
||||
.map(slot => slot.m.eddbID)
|
||||
.filter((v, i, a) => a.indexOf(v) === i);
|
||||
|
||||
// Open up the relevant URL
|
||||
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
|
||||
window.open(
|
||||
'https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the shopping list
|
||||
*/
|
||||
_genShoppingList() {
|
||||
this.context.showModal(<ModalShoppingList ship={this.state.ship} />);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,8 +712,9 @@ export default class OutfittingPage extends Page {
|
||||
_keyDown(e) {
|
||||
// .keyCode will eventually be replaced with .key
|
||||
switch (e.keyCode) {
|
||||
case 69: // 'e'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e
|
||||
case 69: // 'e'
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
// CTRL/CMD + e
|
||||
e.preventDefault();
|
||||
this._exportBuild();
|
||||
}
|
||||
@@ -529,7 +730,28 @@ export default class OutfittingPage extends Page {
|
||||
let state = this.state,
|
||||
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
|
||||
{ translate, units, formats } = language,
|
||||
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
|
||||
{
|
||||
ship,
|
||||
code,
|
||||
savedCode,
|
||||
buildName,
|
||||
newBuildName,
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
mcSys,
|
||||
mcEng,
|
||||
mcWep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep,
|
||||
engagementRange
|
||||
} = state,
|
||||
hide = tooltip.bind(null, null),
|
||||
menu = this.props.currentMenu,
|
||||
shipUpdated = this._shipUpdated,
|
||||
@@ -547,95 +769,288 @@ export default class OutfittingPage extends Page {
|
||||
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
|
||||
const _mStr = ship.getModificationsString();
|
||||
|
||||
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
|
||||
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
|
||||
ship.ladenMass
|
||||
}${cargo}${fuel}`;
|
||||
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
|
||||
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
|
||||
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
|
||||
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
|
||||
const shipSummaryMarker = `${
|
||||
ship.name
|
||||
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
|
||||
|
||||
const requirements = Ships[ship.id].requirements;
|
||||
var requirementElements = [];
|
||||
|
||||
let requirementElements = [];
|
||||
/**
|
||||
* Render the requirements for a ship / etc
|
||||
* @param {string} className Class names
|
||||
* @param {string} textKey The key for translating
|
||||
* @param {String} tooltipTextKey Tooltip key
|
||||
*/
|
||||
function renderRequirement(className, textKey, tooltipTextKey) {
|
||||
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
|
||||
if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
|
||||
requirementElements.push(
|
||||
<div
|
||||
key={textKey}
|
||||
className={className}
|
||||
onMouseEnter={termtip.bind(null, tooltipTextKey)}
|
||||
onMouseLeave={hide}
|
||||
>
|
||||
<a
|
||||
href={
|
||||
textKey.startsWith('empire') ?
|
||||
'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
|
||||
'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{translate(textKey)}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
requirementElements.push(
|
||||
<div
|
||||
key={textKey}
|
||||
className={className}
|
||||
onMouseEnter={termtip.bind(null, tooltipTextKey)}
|
||||
onMouseLeave={hide}
|
||||
>
|
||||
{translate(textKey)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (requirements) {
|
||||
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
|
||||
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
|
||||
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
|
||||
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
|
||||
requirements.federationRank &&
|
||||
renderRequirement(
|
||||
'federation',
|
||||
'federation rank ' + requirements.federationRank,
|
||||
'federation rank required'
|
||||
);
|
||||
requirements.empireRank &&
|
||||
renderRequirement(
|
||||
'empire',
|
||||
'empire rank ' + requirements.empireRank,
|
||||
'empire rank required'
|
||||
);
|
||||
requirements.horizons &&
|
||||
renderRequirement('horizons', 'horizons', 'horizons required');
|
||||
requirements.horizonsEarlyAdoption &&
|
||||
renderRequirement(
|
||||
'horizons',
|
||||
'horizons early adoption',
|
||||
'horizons early adoption required'
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
||||
<div id='overview'>
|
||||
<div
|
||||
id="outfit"
|
||||
className={'page'}
|
||||
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
|
||||
>
|
||||
<div id="overview">
|
||||
<h1>{ship.name}</h1>
|
||||
<div id='requirements'>{requirementElements}</div>
|
||||
<div id='build'>
|
||||
<input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
|
||||
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
|
||||
<FloppyDisk className='lg' />
|
||||
<div id="requirements">{requirementElements}</div>
|
||||
<div id="build">
|
||||
<input
|
||||
value={newBuildName || ''}
|
||||
onChange={this._buildNameChange}
|
||||
placeholder={translate('Enter Name')}
|
||||
maxLength={50}
|
||||
/>
|
||||
<button
|
||||
onClick={canSave && this._saveBuild}
|
||||
disabled={!canSave}
|
||||
onMouseOver={termtip.bind(null, 'save')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<FloppyDisk className="lg" />
|
||||
</button>
|
||||
<button onClick={canRename && this._renameBuild} disabled={!canRename} onMouseOver={termtip.bind(null, 'rename')} onMouseOut={hide}>
|
||||
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>a|</span>
|
||||
<button
|
||||
onClick={canRename && this._renameBuild}
|
||||
disabled={!canRename}
|
||||
onMouseOver={termtip.bind(null, 'rename')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>
|
||||
a|
|
||||
</span>
|
||||
</button>
|
||||
<button onClick={canReload && this._reloadBuild} disabled={!canReload} onMouseOver={termtip.bind(null, 'reload')} onMouseOut={hide}>
|
||||
<Reload className='lg'/>
|
||||
<button
|
||||
onClick={canReload && this._reloadBuild}
|
||||
disabled={!canReload}
|
||||
onMouseOver={termtip.bind(null, 'reload')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<Reload className="lg" />
|
||||
</button>
|
||||
<button className={'danger'} onClick={savedCode && this._deleteBuild} disabled={!savedCode} onMouseOver={termtip.bind(null, 'delete')} onMouseOut={hide}>
|
||||
<Bin className='lg'/>
|
||||
<button
|
||||
className={'danger'}
|
||||
onClick={savedCode && this._deleteBuild}
|
||||
disabled={!savedCode}
|
||||
onMouseOver={termtip.bind(null, 'delete')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<Bin className="lg" />
|
||||
</button>
|
||||
<button onClick={code && this._resetBuild} disabled={!code} onMouseOver={termtip.bind(null, 'reset')} onMouseOut={hide}>
|
||||
<Switch className='lg'/>
|
||||
<button
|
||||
onClick={code && this._resetBuild}
|
||||
disabled={!code}
|
||||
onMouseOver={termtip.bind(null, 'reset')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<Switch className="lg" />
|
||||
</button>
|
||||
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
|
||||
<Download className='lg'/>
|
||||
<button
|
||||
onClick={buildName && this._exportBuild}
|
||||
disabled={!buildName}
|
||||
onMouseOver={termtip.bind(null, 'export')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<Download className="lg" />
|
||||
</button>
|
||||
<button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')} onMouseOut={hide}>
|
||||
<ShoppingIcon className='lg' />
|
||||
<button
|
||||
onClick={this._eddbShoppingList}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<ShoppingIcon className="lg" />
|
||||
</button>
|
||||
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
|
||||
<LinkIcon className='lg' />
|
||||
<button
|
||||
onClick={this._genShortlink}
|
||||
onMouseOver={termtip.bind(null, 'shortlink')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<LinkIcon className="lg" />
|
||||
</button>
|
||||
<button
|
||||
onClick={this._genShoppingList}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
<MatIcon className="lg" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main tables */}
|
||||
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
|
||||
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
|
||||
<ShipSummaryTable
|
||||
ship={ship}
|
||||
fuel={fuel}
|
||||
cargo={cargo}
|
||||
marker={shipSummaryMarker}
|
||||
pips={{
|
||||
sys: this.state.sys,
|
||||
wep: this.state.wep,
|
||||
eng: this.state.eng
|
||||
}}
|
||||
/>
|
||||
<StandardSlotSection
|
||||
ship={ship}
|
||||
fuel={fuel}
|
||||
cargo={cargo}
|
||||
code={standardSlotMarker}
|
||||
onChange={shipUpdated}
|
||||
onCargoChange={this._cargoUpdated}
|
||||
onFuelChange={this._fuelUpdated}
|
||||
currentMenu={menu}
|
||||
sectionMenuRefs={this._sectionMenuRefs}
|
||||
/>
|
||||
<InternalSlotSection
|
||||
ship={ship}
|
||||
code={internalSlotMarker}
|
||||
onChange={shipUpdated}
|
||||
onCargoChange={this._cargoUpdated}
|
||||
onFuelChange={this._fuelUpdated}
|
||||
currentMenu={menu}
|
||||
sectionMenuRefs={this._sectionMenuRefs}
|
||||
/>
|
||||
<HardpointSlotSection
|
||||
ship={ship}
|
||||
code={hardpointsSlotMarker}
|
||||
onChange={shipUpdated}
|
||||
onCargoChange={this._cargoUpdated}
|
||||
onFuelChange={this._fuelUpdated}
|
||||
currentMenu={menu}
|
||||
sectionMenuRefs={this._sectionMenuRefs}
|
||||
/>
|
||||
<UtilitySlotSection
|
||||
ship={ship}
|
||||
code={hardpointsSlotMarker}
|
||||
onChange={shipUpdated}
|
||||
onCargoChange={this._cargoUpdated}
|
||||
onFuelChange={this._fuelUpdated}
|
||||
currentMenu={menu}
|
||||
sectionMenuRefs={this._sectionMenuRefs}
|
||||
/>
|
||||
|
||||
{/* Control of ship and opponent */}
|
||||
<div className='group quarter'>
|
||||
<div className='group half'>
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('ship control')}</h2>
|
||||
<div className="group quarter">
|
||||
<div className="group half">
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
|
||||
{translate('ship control')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<Boost marker={boostMarker} ship={ship} boost={boost} onChange={this._boostUpdated} />
|
||||
<div className="group half">
|
||||
<Boost
|
||||
marker={boostMarker}
|
||||
ship={ship}
|
||||
boost={boost}
|
||||
onChange={this._boostUpdated}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<Pips sys={sys} eng={eng} wep={wep} onChange={this._pipsUpdated} />
|
||||
<div className="group quarter">
|
||||
<Pips
|
||||
sys={sys}
|
||||
eng={eng}
|
||||
wep={wep}
|
||||
mcSys={mcSys}
|
||||
mcEng={mcEng}
|
||||
mcWep={mcWep}
|
||||
onChange={this._pipsUpdated}
|
||||
/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/>
|
||||
<div className="group quarter">
|
||||
<Fuel
|
||||
fuelCapacity={ship.fuelCapacity}
|
||||
fuel={fuel}
|
||||
onChange={this._fuelUpdated}
|
||||
/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
{ ship.cargoCapacity > 0 ? <Cargo cargoCapacity={ship.cargoCapacity} cargo={cargo} onChange={this._cargoUpdated}/> : null }
|
||||
<div className="group quarter">
|
||||
{ship.cargoCapacity > 0 ? (
|
||||
<Cargo
|
||||
cargoCapacity={ship.cargoCapacity}
|
||||
cargo={cargo}
|
||||
onChange={this._cargoUpdated}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<div className='group quarter'>
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('opponent')}</h2>
|
||||
<div className="group half">
|
||||
<div className="group quarter">
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
|
||||
{translate('opponent')}
|
||||
</h2>
|
||||
</div>
|
||||
<div className='group threequarters'>
|
||||
<ShipPicker ship={opponent.id} build={opponentBuild} onChange={this._opponentUpdated}/>
|
||||
<div className="group threequarters">
|
||||
<ShipPicker
|
||||
ship={opponent.id}
|
||||
build={opponentBuild}
|
||||
onChange={this._opponentUpdated}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<EngagementRange ship={ship} engagementRange={engagementRange} onChange={this._engagementRangeUpdated}/>
|
||||
<div className="group half">
|
||||
<EngagementRange
|
||||
ship={ship}
|
||||
engagementRange={engagementRange}
|
||||
onChange={this._engagementRangeUpdated}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tabbed subpages */}
|
||||
|
||||
@@ -51,23 +51,25 @@ export default class Page extends React.Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pages are 'pure' components that only render when props, state, or context changes.
|
||||
* This method performs a shallow comparison to determine change.
|
||||
*
|
||||
* @param {Object} np Next/Incoming properties
|
||||
* @param {Object} ns Next/Incoming state
|
||||
* @param {Object} nc Next/Incoming context
|
||||
* @return {Boolean} True if props, state, or context has changed
|
||||
* Update the window title upon mount
|
||||
*/
|
||||
shouldComponentUpdate(np, ns, nc) {
|
||||
return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
|
||||
componentWillMount() {
|
||||
document.title = this.state.title || 'Coriolis';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the window title upon mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
componentDidMount() {
|
||||
document.title = this.state.title || 'Coriolis';
|
||||
try {
|
||||
(window.adsbygoogle = window.adsbygoogle || []).push({
|
||||
google_ad_client: "ca-pub-3709458261881414",
|
||||
enable_page_level_ads: true
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,9 +22,11 @@ function countHp(slot) {
|
||||
*/
|
||||
function countInt(slot) {
|
||||
let crEligible = !slot.eligible || slot.eligible.cr;
|
||||
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
|
||||
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
|
||||
this.intCount++;
|
||||
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
|
||||
this.maxCargo += crEligible ?
|
||||
ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo :
|
||||
0;
|
||||
|
||||
// if no eligiblity, then assume pce
|
||||
let passSlotType = null;
|
||||
@@ -42,7 +44,9 @@ function countInt(slot) {
|
||||
passSlotType = 'pcq';
|
||||
passSlotRating = 'B';
|
||||
}
|
||||
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
|
||||
let passengerBay = passSlotType ?
|
||||
ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) :
|
||||
null;
|
||||
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
|
||||
}
|
||||
|
||||
@@ -57,23 +61,27 @@ function shipSummary(shipId, shipData) {
|
||||
id: shipId,
|
||||
hpCount: 0,
|
||||
intCount: 0,
|
||||
beta: shipData.beta,
|
||||
maxCargo: 0,
|
||||
maxPassengers: 0,
|
||||
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
||||
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
||||
standard: shipData.slots.standard,
|
||||
agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
|
||||
agility:
|
||||
shipData.properties.pitch +
|
||||
shipData.properties.yaw +
|
||||
shipData.properties.roll
|
||||
};
|
||||
Object.assign(summary, shipData.properties);
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
|
||||
// Build Ship
|
||||
ship.buildWith(shipData.defaults); // Populate with stock/default components
|
||||
ship.buildWith(shipData.defaults); // Populate with stock/default components
|
||||
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
|
||||
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
|
||||
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
|
||||
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
|
||||
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
|
||||
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
|
||||
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
|
||||
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
|
||||
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
|
||||
|
||||
// Best thrusters
|
||||
let th;
|
||||
@@ -97,7 +105,6 @@ function shipSummary(shipId, shipData) {
|
||||
* The Shipyard summary page
|
||||
*/
|
||||
export default class ShipyardPage extends Page {
|
||||
|
||||
static cachedShipSummaries = null;
|
||||
|
||||
/**
|
||||
@@ -119,12 +126,14 @@ export default class ShipyardPage extends Page {
|
||||
title: 'Coriolis EDCD Edition - Shipyard',
|
||||
shipPredicate: 'name',
|
||||
shipDesc: true,
|
||||
shipSummaries: ShipyardPage.cachedShipSummaries
|
||||
shipSummaries: ShipyardPage.cachedShipSummaries,
|
||||
compare: {},
|
||||
groupCompared: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Higlight the current ship in the table
|
||||
* Higlight the current ship in the table on mouse over
|
||||
* @param {String} shipId Ship Id
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
@@ -133,6 +142,24 @@ export default class ShipyardPage extends Page {
|
||||
this.setState({ shipId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle compare highlighting for ships in the table
|
||||
* @param {String} shipId Ship Id
|
||||
*/
|
||||
_toggleCompare(shipId) {
|
||||
let compare = this.state.compare;
|
||||
compare[shipId] = !compare[shipId];
|
||||
this.setState({ compare });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle grouping of compared ships in the table
|
||||
* @private
|
||||
*/
|
||||
_toggleGroupCompared() {
|
||||
this.setState({groupCompared: !this.state.groupCompared})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state with the specified sort predicates
|
||||
* @param {String} shipPredicate Sort predicate - property name
|
||||
@@ -145,12 +172,15 @@ export default class ShipyardPage extends Page {
|
||||
shipPredicateIndex = undefined;
|
||||
}
|
||||
|
||||
if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
|
||||
if (
|
||||
this.state.shipPredicate == shipPredicate &&
|
||||
this.state.shipPredicateIndex == shipPredicateIndex
|
||||
) {
|
||||
shipDesc = !shipDesc;
|
||||
}
|
||||
|
||||
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the table row summary for the ship
|
||||
@@ -159,56 +189,60 @@ export default class ShipyardPage extends Page {
|
||||
* @param {Object} u Localized unit map
|
||||
* @param {Function} fInt Localized integer formatter
|
||||
* @param {Function} fRound Localized round formatter
|
||||
* @param {Boolean} highlight Should this row be highlighted
|
||||
* @return {React.Component} Table Row
|
||||
*/
|
||||
_shipRowElement(s, translate, u, fInt, fRound, highlight) {
|
||||
_shipRowElement(s, translate, u, fInt, fRound) {
|
||||
let noTouch = this.context.noTouch;
|
||||
|
||||
return <tr
|
||||
return (
|
||||
<tr
|
||||
key={s.id}
|
||||
style={{ height: '1.5em' }}
|
||||
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
|
||||
className={cn({
|
||||
highlighted: noTouch && this.state.shipId === s.id,
|
||||
comparehighlight: this.state.compare[s.id],
|
||||
})}
|
||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||
onClick={() => this._toggleCompare(s.id)}
|
||||
>
|
||||
<td className='ri'>{s.manufacturer}</td>
|
||||
<td className='ri'>{fInt(s.retailCost)}</td>
|
||||
<td className='ri cap'>{translate(SizeMap[s.class])}</td>
|
||||
<td className='ri'>{fInt(s.crew)}</td>
|
||||
<td className='ri'>{s.masslock}</td>
|
||||
<td className='ri'>{fInt(s.agility)}</td>
|
||||
<td className='ri'>{fInt(s.hardness)}</td>
|
||||
<td className='ri'>{fInt(s.hullMass)}</td>
|
||||
<td className='ri'>{fInt(s.speed)}</td>
|
||||
<td className='ri'>{fInt(s.boost)}</td>
|
||||
<td className='ri'>{fInt(s.baseArmour)}</td>
|
||||
<td className='ri'>{fInt(s.baseShieldStrength)}</td>
|
||||
<td className='ri'>{fInt(s.topSpeed)}</td>
|
||||
<td className='ri'>{fInt(s.topBoost)}</td>
|
||||
<td className='ri'>{fRound(s.maxJumpRange)}</td>
|
||||
<td className='ri'>{fInt(s.maxCargo)}</td>
|
||||
<td className='ri'>{fInt(s.maxPassengers)}</td>
|
||||
<td className='cn'>{s.standard[0]}</td>
|
||||
<td className='cn'>{s.standard[1]}</td>
|
||||
<td className='cn'>{s.standard[2]}</td>
|
||||
<td className='cn'>{s.standard[3]}</td>
|
||||
<td className='cn'>{s.standard[4]}</td>
|
||||
<td className='cn'>{s.standard[5]}</td>
|
||||
<td className='cn'>{s.standard[6]}</td>
|
||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
||||
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
||||
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
||||
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
|
||||
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
|
||||
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
|
||||
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
|
||||
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
|
||||
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
|
||||
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
|
||||
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
|
||||
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
|
||||
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
|
||||
</tr>;
|
||||
<td className="ri">{fInt(s.retailCost)}</td>
|
||||
<td className="ri cap">{translate(SizeMap[s.class])}</td>
|
||||
<td className="ri">{fInt(s.crew)}</td>
|
||||
<td className="ri">{s.masslock}</td>
|
||||
<td className="ri">{fInt(s.agility)}</td>
|
||||
<td className="ri">{fInt(s.hardness)}</td>
|
||||
<td className="ri">{fInt(s.hullMass)}</td>
|
||||
<td className="ri">{fInt(s.speed)}</td>
|
||||
<td className="ri">{fInt(s.boost)}</td>
|
||||
<td className="ri">{fInt(s.baseArmour)}</td>
|
||||
<td className="ri">{fInt(s.baseShieldStrength)}</td>
|
||||
<td className="ri">{fInt(s.topSpeed)}</td>
|
||||
<td className="ri">{fInt(s.topBoost)}</td>
|
||||
<td className="ri">{fRound(s.maxJumpRange)}</td>
|
||||
<td className="ri">{fInt(s.maxCargo)}</td>
|
||||
<td className="ri">{fInt(s.maxPassengers)}</td>
|
||||
<td className="cn">{s.standard[0]}</td>
|
||||
<td className="cn">{s.standard[1]}</td>
|
||||
<td className="cn">{s.standard[2]}</td>
|
||||
<td className="cn">{s.standard[3]}</td>
|
||||
<td className="cn">{s.standard[4]}</td>
|
||||
<td className="cn">{s.standard[5]}</td>
|
||||
<td className="cn">{s.standard[6]}</td>
|
||||
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
|
||||
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
|
||||
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
|
||||
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
|
||||
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
|
||||
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
|
||||
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
|
||||
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
|
||||
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
|
||||
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
|
||||
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
|
||||
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
|
||||
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,8 +255,9 @@ export default class ShipyardPage extends Page {
|
||||
let hide = this.context.tooltip.bind(null, null);
|
||||
let fInt = formats.int;
|
||||
let fRound = formats.round;
|
||||
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
|
||||
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
|
||||
let { shipSummaries, shipPredicate, shipPredicateIndex, compare, groupCompared } = this.state;
|
||||
let sortShips = (predicate, index) =>
|
||||
this._sortShips.bind(this, predicate, index);
|
||||
|
||||
let filters = {
|
||||
// 'class': { 1: 1, 2: 1}
|
||||
@@ -239,7 +274,8 @@ export default class ShipyardPage extends Page {
|
||||
|
||||
// Sort shipsOverview
|
||||
shipSummaries.sort((a, b) => {
|
||||
let valA = a[shipPredicate], valB = b[shipPredicate];
|
||||
let valA = a[shipPredicate],
|
||||
valB = b[shipPredicate];
|
||||
|
||||
if (shipPredicateIndex != undefined) {
|
||||
valA = valA[shipPredicateIndex];
|
||||
@@ -252,7 +288,16 @@ export default class ShipyardPage extends Page {
|
||||
valB = val;
|
||||
}
|
||||
|
||||
if(valA == valB) {
|
||||
if (groupCompared) {
|
||||
if (compare[a.id] && !compare[b.id]) {
|
||||
return -1;
|
||||
}
|
||||
if (!compare[a.id] && compare[b.id]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (valA == valB) {
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
} else {
|
||||
@@ -269,131 +314,311 @@ export default class ShipyardPage extends Page {
|
||||
let shipRows = new Array(shipSummaries.length);
|
||||
let detailRows = new Array(shipSummaries.length);
|
||||
|
||||
let lastShipSortValue = null;
|
||||
let backgroundHighlight = false;
|
||||
|
||||
for (let s of shipSummaries) {
|
||||
let shipSortValue = s[shipPredicate];
|
||||
if( shipPredicateIndex != undefined ) {
|
||||
shipSortValue = shipSortValue[shipPredicateIndex];
|
||||
}
|
||||
|
||||
if( shipSortValue != lastShipSortValue ) {
|
||||
backgroundHighlight = !backgroundHighlight;
|
||||
lastShipSortValue = shipSortValue;
|
||||
}
|
||||
|
||||
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1, backgroundHighlight);
|
||||
detailRows[i] = this._shipRowElement(
|
||||
s,
|
||||
translate,
|
||||
units,
|
||||
fInt,
|
||||
formats.f1,
|
||||
);
|
||||
shipRows[i] = (
|
||||
<tr
|
||||
key={i}
|
||||
style={{ height: '1.5em' }}
|
||||
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
|
||||
className={cn({
|
||||
highlighted: noTouch && this.state.shipId === s.id,
|
||||
comparehighlight: this.state.compare[s.id],
|
||||
})}
|
||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||
onClick={() => this._toggleCompare(s.id)}
|
||||
>
|
||||
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
|
||||
<td className="le">
|
||||
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
i++;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
|
||||
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
|
||||
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
|
||||
<div className="page" style={{fontSize: sizeRatio + 'em'}}>
|
||||
<div className="content-wrapper">
|
||||
<div className="shipyard-table-wrapper">
|
||||
<table style={{width: '12em', position: 'absolute', zIndex: 1}} className="shipyard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='le rgt'> </th>
|
||||
<th className="le rgt"> </th>
|
||||
</tr>
|
||||
<tr className='main'>
|
||||
<th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
|
||||
<tr className="main">
|
||||
<th className="sortable le rgt" onClick={sortShips('name')}>
|
||||
{translate('ship')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='le rgt invisible'>{units['m/s']}</th>
|
||||
<th className="le rgt invisible">{units['m/s']}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
||||
{shipRows}
|
||||
</tbody>
|
||||
</table>
|
||||
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
|
||||
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
|
||||
<th> </th>
|
||||
<th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
||||
<th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
|
||||
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
|
||||
<th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
|
||||
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
|
||||
<th> </th>
|
||||
<th colSpan={4}>{translate('base')}</th>
|
||||
<th colSpan={5}>{translate('max')}</th>
|
||||
<th className='lft' colSpan={7}></th>
|
||||
<th className='lft' colSpan={5}></th>
|
||||
<th className='lft' colSpan={8}></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
|
||||
<th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
|
||||
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
|
||||
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
|
||||
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
|
||||
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
|
||||
<div style={{ overflowX: 'auto', maxWidth: '100%' }}>
|
||||
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
|
||||
<thead>
|
||||
<tr className="main">
|
||||
<th> </th>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="sortable"
|
||||
onClick={sortShips('class')}
|
||||
>
|
||||
{translate('size')}
|
||||
</th>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="sortable"
|
||||
onClick={sortShips('crew')}
|
||||
>
|
||||
{translate('crew')}
|
||||
</th>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'mass lock factor')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('masslock')}
|
||||
>
|
||||
{translate('MLF')}
|
||||
</th>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="sortable"
|
||||
onClick={sortShips('agility')}
|
||||
>
|
||||
{translate('agility')}
|
||||
</th>
|
||||
<th
|
||||
rowSpan={3}
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'hardness')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('hardness')}
|
||||
>
|
||||
{translate('hrd')}
|
||||
</th>
|
||||
<th> </th>
|
||||
<th colSpan={4}>{translate('base')}</th>
|
||||
<th colSpan={5}>{translate('max')}</th>
|
||||
<th className="lft" colSpan={7} />
|
||||
<th className="lft" colSpan={5} />
|
||||
<th className="lft" colSpan={8} />
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
className="sortable lft"
|
||||
onClick={sortShips('retailCost')}
|
||||
>
|
||||
{translate('cost')}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||
{translate('hull')}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('speed')}>
|
||||
{translate('speed')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('boost')}>
|
||||
{translate('boost')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('baseArmour')}>
|
||||
{translate('armour')}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onClick={sortShips('baseShieldStrength')}
|
||||
>
|
||||
{translate('shields')}
|
||||
</th>
|
||||
|
||||
<th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
|
||||
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
|
||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
|
||||
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
|
||||
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('passengers')}</th>
|
||||
<th className="sortable lft" onClick={sortShips('topSpeed')}>
|
||||
{translate('speed')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('topBoost')}>
|
||||
{translate('boost')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('maxJumpRange')}>
|
||||
{translate('jump')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('maxCargo')}>
|
||||
{translate('cargo')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('maxPassengers')} onMouseEnter={termtip.bind(null, 'passenger capacity')}
|
||||
onMouseLeave={hide}>
|
||||
{translate('pax')}
|
||||
</th>
|
||||
<th className="lft" colSpan={7}>
|
||||
{translate('core module classes')}
|
||||
</th>
|
||||
<th
|
||||
colSpan={5}
|
||||
className="sortable lft"
|
||||
onClick={sortShips('hpCount')}
|
||||
>
|
||||
{translate('hardpoints')}
|
||||
</th>
|
||||
<th
|
||||
colSpan={8}
|
||||
className="sortable lft"
|
||||
onClick={sortShips('intCount')}
|
||||
>
|
||||
{translate('internal compartments')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th
|
||||
className="sortable lft"
|
||||
onClick={sortShips('retailCost')}
|
||||
>
|
||||
{units.CR}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||
{units.T}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('speed')}>
|
||||
{units['m/s']}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('boost')}>
|
||||
{units['m/s']}
|
||||
</th>
|
||||
<th> </th>
|
||||
<th
|
||||
className="sortable"
|
||||
onClick={sortShips('baseShieldStrength')}
|
||||
>
|
||||
{units.MJ}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('topSpeed')}>
|
||||
{units['m/s']}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('topBoost')}>
|
||||
{units['m/s']}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('maxJumpRange')}>
|
||||
{units.LY}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('maxCargo')}>
|
||||
{units.T}
|
||||
</th>
|
||||
<th> </th>
|
||||
<th
|
||||
className="sortable lft"
|
||||
onMouseEnter={termtip.bind(null, 'power plant')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 0)}
|
||||
>
|
||||
{'pp'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'thrusters')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 1)}
|
||||
>
|
||||
{'th'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'frame shift drive')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 2)}
|
||||
>
|
||||
{'fsd'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'life support')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 3)}
|
||||
>
|
||||
{'ls'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'power distriubtor')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 4)}
|
||||
>
|
||||
{'pd'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'sensors')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 5)}
|
||||
>
|
||||
{'s'}
|
||||
</th>
|
||||
<th
|
||||
className="sortable"
|
||||
onMouseEnter={termtip.bind(null, 'fuel tank')}
|
||||
onMouseLeave={hide}
|
||||
onClick={sortShips('standard', 6)}
|
||||
>
|
||||
{'ft'}
|
||||
</th>
|
||||
<th className="sortable lft" onClick={sortShips('hp', 1)}>
|
||||
{translate('S')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('hp', 2)}>
|
||||
{translate('M')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('hp', 3)}>
|
||||
{translate('L')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('hp', 4)}>
|
||||
{translate('H')}
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('hp', 0)}>
|
||||
{translate('U')}
|
||||
</th>
|
||||
|
||||
<th className='lft' colSpan={7}>{translate('core module classes')}</th>
|
||||
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
||||
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
|
||||
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
|
||||
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
|
||||
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
|
||||
<th> </th>
|
||||
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
|
||||
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
|
||||
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
|
||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
|
||||
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
|
||||
<th> </th>
|
||||
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
|
||||
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
|
||||
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
|
||||
<th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
|
||||
|
||||
<th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
|
||||
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
|
||||
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
|
||||
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
|
||||
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
|
||||
<th className='sortable' onClick={sortShips('int', 5)} >6</th>
|
||||
<th className='sortable' onClick={sortShips('int', 6)} >7</th>
|
||||
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
||||
{detailRows}
|
||||
</tbody>
|
||||
</table>
|
||||
<th className="sortable lft" onClick={sortShips('int', 0)}>
|
||||
1
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 1)}>
|
||||
2
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 2)}>
|
||||
3
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 3)}>
|
||||
4
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 4)}>
|
||||
5
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 5)}>
|
||||
6
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 6)}>
|
||||
7
|
||||
</th>
|
||||
<th className="sortable" onClick={sortShips('int', 7)}>
|
||||
8
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
|
||||
{detailRows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="table-tools" >
|
||||
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>Group highlighted ships</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,21 @@ import Module from './Module';
|
||||
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
|
||||
* @param {number} fuel Optional - The fuel consumed during the jump
|
||||
* @return {number} Distance in Light Years
|
||||
* @param {object} ship Ship instance
|
||||
*/
|
||||
export function jumpRange(mass, fsd, fuel) {
|
||||
export function jumpRange(mass, fsd, fuel, ship) {
|
||||
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
||||
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
|
||||
let jumpAddition = 0;
|
||||
if (ship) {
|
||||
mass += ship.reserveFuelCapacity || 0;
|
||||
for (const module of ship.internal) {
|
||||
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
|
||||
jumpAddition += module.m.getJumpBoost();
|
||||
}
|
||||
}
|
||||
}
|
||||
return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,8 +31,9 @@ export function jumpRange(mass, fsd, fuel) {
|
||||
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
|
||||
* @param {number} fuel The total fuel available
|
||||
* @return {number} Distance in Light Years
|
||||
* @param {object} ship Ship instance
|
||||
*/
|
||||
export function totalJumpRange(mass, fsd, fuel) {
|
||||
export function totalJumpRange(mass, fsd, fuel, ship) {
|
||||
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
||||
|
||||
@@ -30,7 +41,7 @@ export function totalJumpRange(mass, fsd, fuel) {
|
||||
let totalRange = 0;
|
||||
while (fuelRemaining > 0) {
|
||||
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
|
||||
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
|
||||
totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship);
|
||||
// Mass is reduced
|
||||
mass -= fuelForThisJump;
|
||||
fuelRemaining -= fuelForThisJump;
|
||||
@@ -45,7 +56,7 @@ export function totalJumpRange(mass, fsd, fuel) {
|
||||
* @param {number} baseShield Base Shield strength MJ for ship
|
||||
* @param {object} sg The shield generator used
|
||||
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
|
||||
* @return {number} Approximate shield strengh in MJ
|
||||
* @return {number} Approximate shield strengh in MJ
|
||||
*/
|
||||
export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
// sg might be a module or a template; handle either here
|
||||
@@ -55,13 +66,12 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
|
||||
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
|
||||
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
|
||||
|
||||
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
let mul = minMul + ynorm * (maxMul - minMul);
|
||||
|
||||
return baseShield * mul * multiplier;
|
||||
return (baseShield * mul * multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +97,16 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pip multiplier for speed.
|
||||
* @param {number} baseSpeed The base speed of ship in data
|
||||
* @param {number} topSpeed The top speed of ship in data
|
||||
* @return {number} The multiplier that pips affect speed.
|
||||
*/
|
||||
export function calcPipSpeed(baseSpeed, topSpeed) {
|
||||
return (topSpeed - baseSpeed) / (4 * topSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pitch of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
@@ -202,7 +222,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
|
||||
* Calculate speed for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseSpeed the base speed of the ship
|
||||
* @param {ojbect} thrusters the thrusters of the ship
|
||||
* @param {object} thrusters the thrusters of the ship
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
@@ -230,7 +250,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
|
||||
* Calculate pitch for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} basePitch the base pitch of the ship
|
||||
* @param {ojbect} thrusters the thrusters of the ship
|
||||
* @param {object} thrusters the thrusters of the ship
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
@@ -325,7 +345,6 @@ export function shieldMetrics(ship, sys) {
|
||||
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
||||
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
||||
const shieldGenerator = shieldGeneratorSlot.m;
|
||||
|
||||
// Boosters
|
||||
let boost = 1;
|
||||
let boosterExplDmg = 1;
|
||||
@@ -339,23 +358,26 @@ export function shieldMetrics(ship, sys) {
|
||||
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate diminishing returns for boosters
|
||||
// Diminishing returns not currently in-game
|
||||
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
|
||||
|
||||
// Remove base shield generator strength
|
||||
boost -= 1;
|
||||
// Apply diminishing returns
|
||||
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
|
||||
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
|
||||
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
|
||||
|
||||
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
|
||||
let shieldAddition = 0;
|
||||
if (ship) {
|
||||
for (const module of ship.internal) {
|
||||
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
|
||||
shieldAddition += module.m.getShieldAddition();
|
||||
}
|
||||
}
|
||||
}
|
||||
let generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
|
||||
const boostersStrength = generatorStrength * boost;
|
||||
|
||||
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
|
||||
const shieldToRecover = (generatorStrength + boostersStrength) / 2;
|
||||
const shieldToRecover = (generatorStrength + boostersStrength + shieldAddition) / 2;
|
||||
const powerDistributor = ship.standard[4].m;
|
||||
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
|
||||
|
||||
@@ -374,7 +396,7 @@ export function shieldMetrics(ship, sys) {
|
||||
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
|
||||
if (sys === 0) {
|
||||
// No system pips so will never recover shields
|
||||
recover = Math.Inf;
|
||||
recover = Math.Infinity;
|
||||
} else {
|
||||
// Recover remaining shields at the rate of the power distributor's recharge
|
||||
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||
@@ -382,7 +404,7 @@ export function shieldMetrics(ship, sys) {
|
||||
}
|
||||
|
||||
// Recharge time is the time taken to go from 50% to 100%
|
||||
const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
|
||||
const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 2;
|
||||
|
||||
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
||||
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
||||
@@ -409,8 +431,10 @@ export function shieldMetrics(ship, sys) {
|
||||
shield = {
|
||||
generator: generatorStrength,
|
||||
boosters: boostersStrength,
|
||||
addition: shieldAddition,
|
||||
cells: ship.shieldCells,
|
||||
total: generatorStrength + boostersStrength + ship.shieldCells,
|
||||
summary: generatorStrength + boostersStrength + shieldAddition,
|
||||
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
||||
recover,
|
||||
recharge,
|
||||
};
|
||||
@@ -425,34 +449,74 @@ export function shieldMetrics(ship, sys) {
|
||||
max: 1 - maxSysResistance
|
||||
};
|
||||
|
||||
/**
|
||||
* An object that stores a selection of difference damage multipliers that
|
||||
* deal with a ship's shield strength.
|
||||
* @typedef {Object} ShieldDamageMults
|
||||
* @property {number} generator Base damage multiplier of the shield
|
||||
* contributing it's base resistance.
|
||||
* @property {number} boosters Damage multiplier contributed by all
|
||||
* boosters, i.e. `rawMj / (generator * boosters)` equals shield strength
|
||||
* with 0 pips to sys.
|
||||
* @property {number} sys Damage multiplier contributed by pips to sys.
|
||||
* @property {number} base Damage multiplier with 0 pips to sys; just
|
||||
* boosters and shield generator. Equals `generator * boosters`.
|
||||
* @property {number} total Damage multiplier with current pip settings.
|
||||
* @property {number} max Damage multiplier with 4 pips to sys.
|
||||
*/
|
||||
|
||||
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
|
||||
let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.explosive = {
|
||||
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
||||
boosters: boosterExplDmg,
|
||||
generator: sgExplosiveDmg,
|
||||
boosters: sgSbExplosiveDmg / sgExplosiveDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
|
||||
base: sgSbExplosiveDmg,
|
||||
total: sgSbExplosiveDmg * (1 - sysResistance),
|
||||
max: sgSbExplosiveDmg * (1 - maxSysResistance),
|
||||
};
|
||||
|
||||
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
|
||||
let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.kinetic = {
|
||||
generator: 1 - shieldGenerator.getKineticResistance(),
|
||||
boosters: boosterKinDmg,
|
||||
generator: sgKineticDmg,
|
||||
boosters: sgSbKineticDmg / sgKineticDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
|
||||
base: sgSbKineticDmg,
|
||||
total: sgSbKineticDmg * (1 - sysResistance),
|
||||
max: sgSbKineticDmg * (1 - maxSysResistance),
|
||||
};
|
||||
|
||||
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
|
||||
let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.thermal = {
|
||||
generator: 1 - shieldGenerator.getThermalResistance(),
|
||||
boosters: boosterThermDmg,
|
||||
generator: sgThermalDmg,
|
||||
boosters: sgSbThermalDmg / sgThermalDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
|
||||
base: sgSbThermalDmg,
|
||||
total: sgSbThermalDmg * (1 - sysResistance),
|
||||
max: sgSbThermalDmg * (1 - maxSysResistance),
|
||||
};
|
||||
}
|
||||
|
||||
return shield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time from one boost to another
|
||||
* @return {number} Boost frequency in seconds
|
||||
* @param {Ship} ship Ship object
|
||||
*/
|
||||
export function calcBoost(ship) {
|
||||
if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) {
|
||||
return undefined;
|
||||
}
|
||||
return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate armour metrics
|
||||
* @param {Object} ship The ship
|
||||
@@ -465,34 +529,29 @@ export function armourMetrics(ship) {
|
||||
|
||||
let moduleArmour = 0;
|
||||
let moduleProtection = 1;
|
||||
|
||||
let hullExplDmg = 1;
|
||||
let hullKinDmg = 1;
|
||||
let hullThermDmg = 1;
|
||||
|
||||
const bulkheads = ship.bulkheads.m;
|
||||
let hullExplDmgs = [];
|
||||
let hullKinDmgs = [];
|
||||
let hullThermDmgs = [];
|
||||
let hullCausDmgs = [];
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of ship.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
if (slot.m && slot.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
|
||||
armourReinforcement += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||
|
||||
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
|
||||
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||
hullExplDmgs.push(1 - slot.m.getExplosiveResistance());
|
||||
hullKinDmgs.push(1 - slot.m.getKineticResistance());
|
||||
hullThermDmgs.push(1 - slot.m.getThermalResistance());
|
||||
hullCausDmgs.push(1 - slot.m.getCausticResistance());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
||||
moduleArmour += slot.m.getIntegrity();
|
||||
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
||||
}
|
||||
}
|
||||
moduleProtection = 1 - moduleProtection;
|
||||
|
||||
// Apply diminishing returns
|
||||
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
|
||||
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
|
||||
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
|
||||
|
||||
const armour = {
|
||||
bulkheads: armourBulkheads,
|
||||
reinforcement: armourReinforcement,
|
||||
@@ -509,24 +568,41 @@ export function armourMetrics(ship) {
|
||||
total: 1
|
||||
};
|
||||
|
||||
let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance();
|
||||
let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs);
|
||||
armour.explosive = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
|
||||
reinforcement: hullExplDmg,
|
||||
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
|
||||
bulkheads: armourExplDmg,
|
||||
reinforcement: armourReinforcedExplDmg / armourExplDmg,
|
||||
total: armourReinforcedExplDmg,
|
||||
res: 1 - armourReinforcedExplDmg
|
||||
};
|
||||
|
||||
let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance();
|
||||
let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs);
|
||||
armour.kinetic = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
|
||||
reinforcement: hullKinDmg,
|
||||
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
|
||||
bulkheads: armourKinDmg,
|
||||
reinforcement: armourReinforcedKinDmg / armourKinDmg,
|
||||
total: armourReinforcedKinDmg,
|
||||
res: 1 - armourReinforcedKinDmg
|
||||
};
|
||||
|
||||
let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance();
|
||||
let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs);
|
||||
armour.thermal = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
|
||||
reinforcement: hullThermDmg,
|
||||
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
|
||||
bulkheads: armourThermDmg,
|
||||
reinforcement: armourReinforcedThermDmg / armourThermDmg,
|
||||
total: armourReinforcedThermDmg,
|
||||
res: 1 - armourReinforcedThermDmg
|
||||
};
|
||||
|
||||
let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance();
|
||||
let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs);
|
||||
armour.caustic = {
|
||||
bulkheads: armourCausDmg,
|
||||
reinforcement: armourReinforcedCausDmg / armourCausDmg,
|
||||
total: armourReinforcedCausDmg,
|
||||
res: 1 - armourReinforcedCausDmg,
|
||||
};
|
||||
return armour;
|
||||
}
|
||||
|
||||
@@ -698,20 +774,55 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
|
||||
return { shieldsdps, armoursdps, eps };
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores SDPS split up by type.
|
||||
* @typedef {Object} SDps
|
||||
* @property {number} absolute Damage of type absolute
|
||||
* @property {number} explosive Damage of type explosive
|
||||
* @property {number} kinetic Damage of type kinetic
|
||||
* @property {number} thermal Damage of type thermal
|
||||
* @property {number} [total] Sum of all damage types
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object that holds information about SDPS for a given weapon and opponent.
|
||||
* @typedef {Object} WeaponDamage
|
||||
* @property {number} eps Energy per second
|
||||
* @property {Object} damage An object that stores damage inflicted by
|
||||
* the weapon.
|
||||
* @property {Object} effectiveness An object that stores the effectiveness of
|
||||
* the weapon against the opponent given.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores overall SDPS and against a given opponent's shields and armour.
|
||||
* @typedef {Object} WeaponDamage~damage
|
||||
* @property {SDps} base Overall SDPS.
|
||||
* @property {SDps} shields SDPS against the given opponent's shields.
|
||||
* @property {SDps} armour SDPS against the given opponent's armour.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a weapon at a given range
|
||||
* @param {Object} m The weapon
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shield resistances
|
||||
* @param {Object} opponentArmour The opponent's armour resistances
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Sustained DPS for shield and armour
|
||||
* @returns {WeaponDamage} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const opponentHasShields = opponentShields.generator ? true : false;
|
||||
const weapon = {
|
||||
eps: 0,
|
||||
damage: {
|
||||
base: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0,
|
||||
},
|
||||
shields: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
@@ -731,12 +842,14 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
shields: {
|
||||
range: 1,
|
||||
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
|
||||
resistance: 1
|
||||
resistance: 1,
|
||||
dpe: 1
|
||||
},
|
||||
armour: {
|
||||
range: 1,
|
||||
hardness: 1,
|
||||
resistance: 1
|
||||
resistance: 1,
|
||||
dpe: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -745,7 +858,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
|
||||
|
||||
// Initial sustained DPS
|
||||
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
|
||||
let sDps = m.getSDps();
|
||||
|
||||
// Take fall-off in to account
|
||||
const falloff = m.getFalloff();
|
||||
@@ -756,6 +869,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
sDps *= dropoff;
|
||||
}
|
||||
|
||||
weapon.damage.base.absolute = sDps * m.getDamageDist().A;
|
||||
weapon.damage.base.explosive = sDps * m.getDamageDist().E;
|
||||
weapon.damage.base.kinetic = sDps * m.getDamageDist().K;
|
||||
weapon.damage.base.thermal = sDps * m.getDamageDist().T;
|
||||
weapon.damage.base.total = sDps;
|
||||
|
||||
// Piercing/hardness modifier (for armour only)
|
||||
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
||||
weapon.effectiveness.armour.hardness = armourMultiple;
|
||||
@@ -790,11 +909,19 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
|
||||
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
|
||||
|
||||
|
||||
|
||||
weapon.effectiveness.shields.resistance *= shieldsResistance;
|
||||
weapon.effectiveness.armour.resistance *= armourResistance;
|
||||
|
||||
|
||||
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
|
||||
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
|
||||
|
||||
weapon.effectiveness.shields.dpe = weapon.damage.shields.total / m.getEps();
|
||||
weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps();
|
||||
|
||||
|
||||
return weapon;
|
||||
}
|
||||
|
||||
@@ -836,7 +963,10 @@ export function timeToDrainWep(ship, wep) {
|
||||
*/
|
||||
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||
const drainPerSecond = eps - recharge;
|
||||
if (drainPerSecond <= 0) {
|
||||
// If there is nothing to remove, we're don instantly
|
||||
if (!amount) {
|
||||
return 0;
|
||||
} if (drainPerSecond <= 0) {
|
||||
// Simple result
|
||||
return amount / dps;
|
||||
} else {
|
||||
@@ -853,3 +983,52 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether diminishing returns should be applied to shield damage
|
||||
* multipliers and does so if necessary.
|
||||
* @param {number} shieldMult Damage multiplier of shield generator
|
||||
* @param {number} combinedMult Damage multiplier of shields and shield boosters
|
||||
* @returns {number} Overall damage multiplier
|
||||
*/
|
||||
export function diminishingReturnsShields(shieldMult, combinedMult) {
|
||||
let max = shieldMult * 0.7;
|
||||
if (combinedMult < max) {
|
||||
return mapIntoDiminishingRange(max / 2, max, combinedMult);
|
||||
} else {
|
||||
return combinedMult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether diminishing returns should be applied to armour damage
|
||||
* multipliers and does so if necessary.
|
||||
* @param {...any} mults Damage multipliers of alloys and hull reinforcement
|
||||
* packages
|
||||
* @returns {number} Overall damage multiplier
|
||||
*/
|
||||
export function diminishingReturnsArmour(...mults) {
|
||||
let max = Math.min(0.7, ...mults);
|
||||
let combined = mults.reduce((aggr, v) => aggr * v);
|
||||
let diminished = mapIntoDiminishingRange(0.35, max, combined);
|
||||
if (diminished < 0.7) {
|
||||
return diminished;
|
||||
} else {
|
||||
return combined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies diminishing returns to a damage multiplier. Effictively, the range
|
||||
* [`0`, `max`]` is mapped into the range [`min`, `max`] for the value `now`.
|
||||
* It can also happen, that `now` is outside of the range [`min`, `max`], then
|
||||
* `now` is actually improved, i.e. enlarged.
|
||||
* @param {number} min Best theoretical damage multiplier
|
||||
* @param {number} max Damage multiplier from which diminishing returns start to
|
||||
* be applied
|
||||
* @param {number} now The current damage multiplier
|
||||
* @returns {number} Remapped damage multiplier
|
||||
*/
|
||||
export function mapIntoDiminishingRange(min, max, now) {
|
||||
return min + (max - min) * (now / max);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ export const StandardArray = [
|
||||
'pd', // Power Distributor
|
||||
's', // Sensors
|
||||
'ft', // Fuel Tank
|
||||
'gpp', // Guardian Hybrid Power Plant
|
||||
'gpd' // Guardian Hybrid Power Distributor
|
||||
];
|
||||
|
||||
// Map to lookup group labels/names for component grp, used for JSON Serialization
|
||||
export const ModuleGroupToName = {
|
||||
// Standard
|
||||
pp: 'Power Plant',
|
||||
gpp: 'Guardian Hybrid Power Plant',
|
||||
gpd: 'Guardian Power Distributor',
|
||||
t: 'Thrusters',
|
||||
fsd: 'Frame Shift Drive',
|
||||
ls: 'Life Support',
|
||||
@@ -48,6 +52,12 @@ export const ModuleGroupToName = {
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
cc: 'Collector Limpet Controller',
|
||||
ss: 'Surface Scanner',
|
||||
gsrp: 'Guardian Shield Reinforcement Packages',
|
||||
gfsb: 'Guardian Frame Shift Drive Booster',
|
||||
ghrp: 'Guardian Hull Reinforcement Package',
|
||||
gmrp: 'Guardian Module Reinforcement Package',
|
||||
mahr: 'Meta Alloy Hull Reinforcement Package',
|
||||
sua: 'Supercruise Assist',
|
||||
|
||||
// Hard Points
|
||||
bl: 'Beam Laser',
|
||||
@@ -75,7 +85,20 @@ export const ModuleGroupToName = {
|
||||
sb: 'Shield Booster',
|
||||
tp: 'Torpedo Pylon',
|
||||
sfn: 'Shutdown Field Neutraliser',
|
||||
xs: 'Xeno Scanner'
|
||||
xs: 'Xeno Scanner',
|
||||
rcpl: 'Recon Limpet Controller',
|
||||
rsl: 'Research Limpet Controller',
|
||||
dtl: 'Decontamination Limpet Controller',
|
||||
gpc: 'Guardian Plasma Charger',
|
||||
ggc: 'Guardian Gauss Cannon',
|
||||
tbsc: 'Shock Cannon',
|
||||
gsc: 'Guardian Shard Cannon',
|
||||
tbem: 'Enzyme Missile Rack',
|
||||
tbrfl: 'Remote Release Flechette Launcher',
|
||||
pwa: 'Pulse Wave Analyser',
|
||||
abl: 'Abrasion Blaster',
|
||||
scl: 'Seismic Charge Launcher',
|
||||
sdm: 'Sub-Surface Displacement Missile',
|
||||
};
|
||||
|
||||
let GrpNameToCodeMap = {};
|
||||
@@ -184,7 +207,7 @@ export const ShipFacets = [
|
||||
i: 9
|
||||
},
|
||||
{ // 10
|
||||
title: 'fastest range',
|
||||
title: 'farthest range',
|
||||
props: ['unladenFastestRange', 'ladenFastestRange'],
|
||||
lbls: ['unladen', 'laden'],
|
||||
unit: 'LY',
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Modification - a modification and its value
|
||||
*/
|
||||
export default class Modification {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique modification ID
|
||||
* @param {Number} value Value of the modification
|
||||
@@ -11,5 +10,4 @@ export default class Modification {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,6 @@ function filter(arr, maxClass, minClass, mass) {
|
||||
* The available module set for a specific ship
|
||||
*/
|
||||
export default class ModuleSet {
|
||||
|
||||
/**
|
||||
* Instantiate the module set
|
||||
* @param {Object} modules All Modules
|
||||
|
||||
@@ -63,7 +63,10 @@ export function standard(type, id) {
|
||||
if (!isNaN(type)) {
|
||||
type = StandardArray[type];
|
||||
}
|
||||
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
|
||||
let s = Modules.standard[type].find(e => e.id === id);
|
||||
if (!s) {
|
||||
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
|
||||
}
|
||||
if (s) {
|
||||
s = new Module({ template: s });
|
||||
}
|
||||
|
||||
@@ -85,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
|
||||
code = ship.toString();
|
||||
|
||||
let data = {
|
||||
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
|
||||
$schema: 'https://coriolis.io/schemas/ship-loadout/4.json#',
|
||||
name: buildName,
|
||||
ship: ship.name,
|
||||
references: [{
|
||||
name: 'Coriolis.io',
|
||||
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
|
||||
url: 'https://coriolis.io' + outfitURL(ship.id, code, buildName),
|
||||
code,
|
||||
shipId: ship.id
|
||||
}],
|
||||
|
||||
@@ -7,9 +7,10 @@ import LZString from 'lz-string';
|
||||
import * as _ from 'lodash';
|
||||
import isEqual from 'lodash/lang';
|
||||
import { Ships, Modifications } from 'coriolis-data/dist';
|
||||
import { chain } from 'lodash';
|
||||
const zlib = require('zlib');
|
||||
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb', 'dc'];
|
||||
|
||||
// Constants for modifications struct
|
||||
const SLOT_ID_DONE = -1;
|
||||
@@ -70,7 +71,6 @@ function reduceToIDs(idArray, slot, slotIndex) {
|
||||
* Ship Model - Encapsulates and models in-game ship behavior
|
||||
*/
|
||||
export default class Ship {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique ship Id / Key
|
||||
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
|
||||
@@ -151,7 +151,7 @@ export default class Ship {
|
||||
* @return {Number} Jump range in Light Years
|
||||
*/
|
||||
calcLadenRange(massDelta, fuel, fsd) {
|
||||
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel);
|
||||
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +164,7 @@ export default class Ship {
|
||||
calcUnladenRange(massDelta, fuel, fsd) {
|
||||
fsd = fsd || this.standard[2].m;
|
||||
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +239,6 @@ export default class Ship {
|
||||
}
|
||||
sg = sgSlot.m;
|
||||
}
|
||||
|
||||
// TODO Not accurate if the ship has modified shield boosters
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
|
||||
}
|
||||
@@ -416,16 +415,16 @@ export default class Ship {
|
||||
clearModifications(m) {
|
||||
m.mods = {};
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,12 +436,15 @@ export default class Ship {
|
||||
m.blueprint = bp;
|
||||
this.clearModifications(m);
|
||||
// Set any hidden items for the blueprint now
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
if (Modifications.modifications[featureName].hidden) {
|
||||
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
|
||||
if (m.blueprint.grades[m.blueprint.grade] && m.blueprint.grades[m.blueprint.grade].features) {
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
if (Modifications.modifications[featureName].hidden) {
|
||||
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateModificationsString();
|
||||
}
|
||||
|
||||
@@ -464,7 +466,17 @@ export default class Ship {
|
||||
if (m.blueprint) {
|
||||
m.blueprint.special = special;
|
||||
}
|
||||
this.recalculateDps().recalculateHps().recalculateEps();
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,10 +484,7 @@ export default class Ship {
|
||||
* @param {Object} m The module for which to clear the blueprint
|
||||
*/
|
||||
clearModuleSpecial(m) {
|
||||
if (m.blueprint) {
|
||||
m.blueprint.special = null;
|
||||
}
|
||||
this.recalculateDps().recalculateHps().recalculateEps();
|
||||
this.setModuleSpecial(m, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,68 +493,67 @@ export default class Ship {
|
||||
* @param {Object} name The name of the modification to change
|
||||
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
|
||||
* @param {bool} sentfromui True if this update was sent from the UI
|
||||
* @param {bool} isAbsolute True if value is an absolute value and not a
|
||||
* modification value
|
||||
*/
|
||||
setModification(m, name, value, sentfromui) {
|
||||
setModification(m, name, value, sentfromui, isAbsolute) {
|
||||
if (isNaN(value)) {
|
||||
// Value passed is invalid; reset it to 0
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (isAbsolute) {
|
||||
m.setPretty(name, value, sentfromui);
|
||||
} else {
|
||||
// Resistance modifiers scale with the base value
|
||||
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
|
||||
let baseValue = m.get(name, false);
|
||||
value = (1 - baseValue) * value;
|
||||
}
|
||||
m.setModValue(name, value, sentfromui);
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (name === 'pgen') {
|
||||
// Power generation
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updatePowerGenerated();
|
||||
} else if (name === 'power') {
|
||||
// Power usage
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updatePowerUsed();
|
||||
} else if (name === 'mass') {
|
||||
// Mass
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateMass();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
} else if (name === 'maxfuel') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updateJumpStats();
|
||||
} else if (name === 'optmass') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'optmul') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'shieldboost') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateShield();
|
||||
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateArmour();
|
||||
} else if (name === 'shieldreinforcement') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateShieldCells();
|
||||
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateDps();
|
||||
this.recalculateHps();
|
||||
this.recalculateEps();
|
||||
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Could be for shields or armour
|
||||
this.recalculateArmour();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'engcap') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Might have resulted in a change in boostability
|
||||
this.updateMovement();
|
||||
} else {
|
||||
// Generic
|
||||
m.setModValue(name, value, sentfromui);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,16 +700,16 @@ export default class Ship {
|
||||
// Update aggragated stats
|
||||
if (comps) {
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
@@ -928,9 +936,14 @@ export default class Ship {
|
||||
let epsChanged = n && n.getEps() || old && old.getEps();
|
||||
let hpsChanged = n && n.getHps() || old && old.getHps();
|
||||
|
||||
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp');
|
||||
let armourChange = (slot === this.bulkheads) ||
|
||||
(n && n.grp === 'hr') || (old && old.grp === 'hr') ||
|
||||
(n && n.grp === 'ghrp') || (old && old.grp === 'ghrp') ||
|
||||
(n && n.grp == 'mahr') || (old && old.grp == 'mahr') ||
|
||||
(n && n.grp === 'mrp') || (old && old.grp === 'mrp') ||
|
||||
(n && n.grp === 'gmrp') || (old && old.grp == 'gmrp');
|
||||
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb');
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb') || (old && old.grp === 'gsrp') || (n && n.grp === 'gsrp');
|
||||
|
||||
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
|
||||
|
||||
@@ -987,25 +1000,6 @@ export default class Ship {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate diminishing returns value, where values below a given limit are returned
|
||||
* as-is, and values between the lower and upper limit of the diminishing returns are
|
||||
* given at half value.
|
||||
* Commonly used for resistances.
|
||||
* @param {Number} val The value
|
||||
* @param {Number} drll The lower limit for diminishing returns
|
||||
* @param {Number} drul The upper limit for diminishing returns
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
diminishingReturns(val, drll, drul) {
|
||||
if (val < drll) {
|
||||
val = drll;
|
||||
} else if (val < drul) {
|
||||
val = drul - (drul - val) / 2;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate damage per second and related items for weapons
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
@@ -1192,36 +1186,35 @@ export default class Ship {
|
||||
|
||||
unladenMass += this.bulkheads.m.getMass();
|
||||
|
||||
for (let slotNum in this.standard) {
|
||||
const slot = this.standard[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
if (slot.m.grp === 'ft') {
|
||||
fuelCapacity += slot.m.fuel;
|
||||
}
|
||||
}
|
||||
}
|
||||
let slots = this.standard.concat(this.internal, this.hardpoints);
|
||||
// TODO: create class for slot and also add slot.get
|
||||
// handle unladen mass
|
||||
unladenMass += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('mass') : null)
|
||||
.map(mass => mass || 0)
|
||||
.reduce((sum, mass) => sum + mass)
|
||||
.value();
|
||||
|
||||
for (let slotNum in this.internal) {
|
||||
const slot = this.internal[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
if (slot.m.grp === 'ft') {
|
||||
fuelCapacity += slot.m.fuel;
|
||||
} else if (slot.m.grp === 'cr') {
|
||||
cargoCapacity += slot.m.cargo;
|
||||
} else if (slot.m.grp.slice(0,2) === 'pc') {
|
||||
passengerCapacity += slot.m.passengers
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle fuel capacity
|
||||
fuelCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('fuel') : null)
|
||||
.map(fuel => fuel || 0)
|
||||
.reduce((sum, fuel) => sum + fuel)
|
||||
.value();
|
||||
|
||||
for (let slotNum in this.hardpoints) {
|
||||
const slot = this.hardpoints[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
}
|
||||
}
|
||||
// handle cargo capacity
|
||||
cargoCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('cargo') : null)
|
||||
.map(cargo => cargo || 0)
|
||||
.reduce((sum, cargo) => sum + cargo)
|
||||
.value();
|
||||
|
||||
// handle passenger capacity
|
||||
passengerCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('passengers') : null)
|
||||
.map(passengers => passengers || 0)
|
||||
.reduce((sum, passengers) => sum + passengers)
|
||||
.value();
|
||||
|
||||
// Update global stats
|
||||
this.unladenMass = unladenMass;
|
||||
@@ -1262,7 +1255,7 @@ export default class Ship {
|
||||
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
|
||||
const metrics = Calc.shieldMetrics(this, 0);
|
||||
|
||||
this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0;
|
||||
this.shield = metrics.generator ? metrics.generator + metrics.boosters + metrics.addition : 0;
|
||||
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
|
||||
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
|
||||
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
|
||||
@@ -1295,45 +1288,15 @@ export default class Ship {
|
||||
*/
|
||||
recalculateArmour() {
|
||||
// Armour from bulkheads
|
||||
let bulkhead = this.bulkheads.m;
|
||||
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
|
||||
let modulearmour = 0;
|
||||
let moduleprotection = 1;
|
||||
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
||||
const hullExplResDRStart = hullExplRes * 0.7;
|
||||
const hullExplResDREnd = hullExplRes * 0;
|
||||
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
||||
const hullKinResDRStart = hullKinRes * 0.7;
|
||||
const hullKinResDREnd = hullKinRes * 0;
|
||||
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
||||
const hullThermResDRStart = hullThermRes * 0.7;
|
||||
const hullThermResDREnd = hullThermRes * 0;
|
||||
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
armour += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||
|
||||
hullExplRes *= (1 - slot.m.getExplosiveResistance());
|
||||
hullKinRes *= (1 - slot.m.getKineticResistance());
|
||||
hullThermRes *= (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
modulearmour += slot.m.getIntegrity();
|
||||
moduleprotection = moduleprotection * (1 - slot.m.getProtection());
|
||||
}
|
||||
}
|
||||
moduleprotection = 1 - moduleprotection;
|
||||
|
||||
this.armour = armour;
|
||||
this.modulearmour = modulearmour;
|
||||
this.moduleprotection = moduleprotection;
|
||||
this.hullExplRes = 1 - this.diminishingReturns(hullExplRes, hullExplResDREnd, hullExplResDRStart);
|
||||
this.hullKinRes = 1 - this.diminishingReturns(hullKinRes, hullKinResDREnd, hullKinResDRStart);
|
||||
this.hullThermRes = 1 - this.diminishingReturns(hullThermRes, hullThermResDREnd, hullThermResDRStart);
|
||||
let metrics = Calc.armourMetrics(this);
|
||||
|
||||
this.armour = metrics.total ? metrics.total : 0;
|
||||
this.modulearmour = metrics.modulearmour;
|
||||
this.moduleprotection = metrics.moduleprotection;
|
||||
this.hullExplRes = 1 - metrics.explosive.total;
|
||||
this.hullKinRes = 1 - metrics.kinetic.total;
|
||||
this.hullThermRes = 1 - metrics.thermal.total;
|
||||
this.hullCausRes = 1 - metrics.caustic.total;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1345,10 +1308,10 @@ export default class Ship {
|
||||
let fsd = this.standard[2].m; // Frame Shift Drive;
|
||||
let { unladenMass, fuelCapacity } = this;
|
||||
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
|
||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
|
||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, fuelCapacity, this); // Full Tank
|
||||
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
|
||||
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
|
||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
|
||||
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
|
||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
||||
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
|
||||
return this;
|
||||
}
|
||||
@@ -1543,7 +1506,7 @@ export default class Ship {
|
||||
} else {
|
||||
buffer.writeInt32LE(slotMod.value, curpos);
|
||||
}
|
||||
// const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
|
||||
const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
|
||||
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
|
||||
curpos += 4;
|
||||
}
|
||||
@@ -1556,6 +1519,7 @@ export default class Ship {
|
||||
}
|
||||
|
||||
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
|
||||
// console.log(this.serialized.modifications)
|
||||
} else {
|
||||
this.serialized.modifications = null;
|
||||
}
|
||||
@@ -1724,11 +1688,11 @@ export default class Ship {
|
||||
updated;
|
||||
|
||||
this.useBulkhead(0)
|
||||
.use(standard[2], fsd) // FSD
|
||||
.use(standard[3], ls) // Life Support
|
||||
.use(standard[5], s) // Sensors
|
||||
.use(standard[4], pd) // Power Distributor
|
||||
.use(standard[6], ft); // Fuel Tank
|
||||
.use(standard[2], fsd) // FSD
|
||||
.use(standard[3], ls) // Life Support
|
||||
.use(standard[5], s) // Sensors
|
||||
.use(standard[4], pd) // Power Distributor
|
||||
.use(standard[6], ft); // Fuel Tank
|
||||
|
||||
// Turn off nearly everything
|
||||
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as ModuleUtils from './ModuleUtils'
|
||||
import { canMount } from '../utils/SlotFunctions'
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
|
||||
/**
|
||||
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
|
||||
@@ -7,20 +7,20 @@ import { canMount } from '../utils/SlotFunctions'
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
||||
*/
|
||||
export function multiPurpose (ship, shielded, bulkheadIndex) {
|
||||
export function multiPurpose(ship, shielded, bulkheadIndex) {
|
||||
ship.useStandard('A')
|
||||
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
|
||||
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
|
||||
.useBulkhead(bulkheadIndex)
|
||||
.useBulkhead(bulkheadIndex);
|
||||
|
||||
if (shielded) {
|
||||
ship.internal.some(function (slot) {
|
||||
ship.internal.some(function(slot) {
|
||||
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'))
|
||||
ship.setSlotEnabled(slot, true)
|
||||
return true
|
||||
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, true);
|
||||
return true;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,52 +30,51 @@ export function multiPurpose (ship, shielded, bulkheadIndex) {
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
* @param {Object} standardOpts [Optional] Standard module optional overrides
|
||||
*/
|
||||
export function trader (ship, shielded, standardOpts) {
|
||||
let usedSlots = []
|
||||
let bstCount = 2
|
||||
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
|
||||
export function trader(ship, shielded, standardOpts) {
|
||||
let usedSlots = [];
|
||||
let bstCount = 2;
|
||||
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
ship.useStandard('A')
|
||||
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
|
||||
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
|
||||
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
|
||||
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
|
||||
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors
|
||||
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
|
||||
shieldInternals.some(function (slot) {
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
shieldInternals.some(function(slot) {
|
||||
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A')
|
||||
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
|
||||
if (shield && shield.maxmass > ship.hullMass) {
|
||||
console.log(shield)
|
||||
ship.use(slot, shield)
|
||||
ship.setSlotEnabled(slot, true)
|
||||
usedSlots.push(slot)
|
||||
return true
|
||||
ship.use(slot, shield);
|
||||
ship.setSlotEnabled(slot, true);
|
||||
usedSlots.push(slot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Fill the empty internals with cargo racks
|
||||
for (let i = ship.internal.length; i--;) {
|
||||
let slot = ship.internal[i]
|
||||
let slot = ship.internal[i];
|
||||
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'))
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
}
|
||||
|
||||
// Empty the hardpoints
|
||||
for (let s of ship.hardpoints) {
|
||||
ship.use(s, null)
|
||||
ship.use(s, null);
|
||||
}
|
||||
for (let s of ship.hardpoints) {
|
||||
if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters
|
||||
ship.use(s, ModuleUtils.hardpoints('04'))
|
||||
bstCount--
|
||||
ship.use(s, ModuleUtils.hardpoints('04'));
|
||||
bstCount--;
|
||||
} else {
|
||||
ship.use(s, null)
|
||||
ship.use(s, null);
|
||||
}
|
||||
}
|
||||
// ship.useLightestStandard(standardOpts);
|
||||
@@ -86,127 +85,127 @@ export function trader (ship, shielded, standardOpts) {
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
||||
*/
|
||||
export function explorer (ship, planetary) {
|
||||
let standardOpts = {ppRating: 'A'},
|
||||
heatSinkCount = 2, // Fit 2 heat sinks if possible
|
||||
usedSlots = [],
|
||||
sgSlot,
|
||||
fuelScoopSlot,
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
|
||||
export function explorer(ship, planetary) {
|
||||
let standardOpts = { ppRating: 'A' },
|
||||
heatSinkCount = 2, // Fit 2 heat sinks if possible
|
||||
usedSlots = [],
|
||||
sgSlot,
|
||||
fuelScoopSlot,
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
if (!planetary) { // Non-planetary explorers don't really need to boost
|
||||
standardOpts.pd = '1D'
|
||||
standardOpts.pd = '1D';
|
||||
}
|
||||
|
||||
// Cargo hatch can be disabled
|
||||
ship.setSlotEnabled(ship.cargoHatch, false)
|
||||
ship.setSlotEnabled(ship.cargoHatch, false);
|
||||
|
||||
// Advanced Discovery Scanner - class 1 or higher
|
||||
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sc)
|
||||
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < adsInternals.length; i++) {
|
||||
if (canMount(ship, adsInternals[i], 'sc')) {
|
||||
ship.use(adsInternals[i], ModuleUtils.internal('2f'))
|
||||
usedSlots.push(adsInternals[i])
|
||||
break
|
||||
ship.use(adsInternals[i], ModuleUtils.internal('2f'));
|
||||
usedSlots.push(adsInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (planetary) {
|
||||
// Planetary Vehicle Hangar - class 2 or higher
|
||||
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1]
|
||||
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
|
||||
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pv)
|
||||
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < pvhInternals.length; i++) {
|
||||
if (canMount(ship, pvhInternals[i], 'pv')) {
|
||||
// Planetary Vehical Hangar only has even classes
|
||||
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass
|
||||
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')) // G is lower mass
|
||||
ship.setSlotEnabled(pvhInternals[i], false) // Disable power for Planetary Vehical Hangar
|
||||
usedSlots.push(pvhInternals[i])
|
||||
break
|
||||
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass;
|
||||
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass
|
||||
ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar
|
||||
usedSlots.push(pvhInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < shieldInternals.length; i++) {
|
||||
if (canMount(ship, shieldInternals[i], 'sg')) {
|
||||
ship.use(shieldInternals[i], sg)
|
||||
usedSlots.push(shieldInternals[i])
|
||||
sgSlot = shieldInternals[i]
|
||||
break
|
||||
ship.use(shieldInternals[i], sg);
|
||||
usedSlots.push(shieldInternals[i]);
|
||||
sgSlot = shieldInternals[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed Surface Scanner
|
||||
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sc)
|
||||
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < dssInternals.length; i++) {
|
||||
if (canMount(ship, dssInternals[i], 'sc')) {
|
||||
ship.use(dssInternals[i], ModuleUtils.internal('2i'))
|
||||
usedSlots.push(dssInternals[i])
|
||||
break
|
||||
ship.use(dssInternals[i], ModuleUtils.internal('2i'));
|
||||
usedSlots.push(dssInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fuel scoop - best possible
|
||||
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1]
|
||||
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
|
||||
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.fs)
|
||||
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < fuelScoopInternals.length; i++) {
|
||||
if (canMount(ship, fuelScoopInternals[i], 'fs')) {
|
||||
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'))
|
||||
usedSlots.push(fuelScoopInternals[i])
|
||||
fuelScoopSlot = fuelScoopInternals[i]
|
||||
break
|
||||
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
|
||||
usedSlots.push(fuelScoopInternals[i]);
|
||||
fuelScoopSlot = fuelScoopInternals[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// AFMUs - fill as they are 0-weight
|
||||
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1]
|
||||
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
|
||||
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pc)
|
||||
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < afmuInternals.length; i++) {
|
||||
if (canMount(ship, afmuInternals[i], 'am')) {
|
||||
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'))
|
||||
usedSlots.push(afmuInternals[i])
|
||||
ship.setSlotEnabled(afmuInternals[i], false) // Disable power for AFM Unit
|
||||
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
|
||||
usedSlots.push(afmuInternals[i]);
|
||||
ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit
|
||||
}
|
||||
}
|
||||
|
||||
for (let s of ship.hardpoints) {
|
||||
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
|
||||
ship.use(s, ModuleUtils.hardpoints('02'))
|
||||
ship.setSlotEnabled(s, heatSinkCount == 2) // Only enable a single Heatsink
|
||||
heatSinkCount--
|
||||
ship.use(s, ModuleUtils.hardpoints('02'));
|
||||
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
|
||||
heatSinkCount--;
|
||||
} else {
|
||||
ship.use(s, null)
|
||||
ship.use(s, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (sgSlot && fuelScoopSlot) {
|
||||
// The SG and Fuel scoop to not need to be powered at the same time
|
||||
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
|
||||
ship.setSlotEnabled(fuelScoopSlot, false)
|
||||
ship.setSlotEnabled(fuelScoopSlot, false);
|
||||
} else { // The Fuel scoop uses the most power
|
||||
ship.setSlotEnabled(sgSlot, false)
|
||||
ship.setSlotEnabled(sgSlot, false);
|
||||
}
|
||||
}
|
||||
|
||||
ship.useLightestStandard(standardOpts)
|
||||
ship.useLightestStandard(standardOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,188 +213,188 @@ export function explorer (ship, planetary) {
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
export function miner (ship, shielded) {
|
||||
shielded = true
|
||||
let standardOpts = {ppRating: 'A'},
|
||||
miningLaserCount = 2,
|
||||
usedSlots = [],
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
|
||||
export function miner(ship, shielded) {
|
||||
shielded = true;
|
||||
let standardOpts = { ppRating: 'A' },
|
||||
miningLaserCount = 2,
|
||||
usedSlots = [],
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
// Cargo hatch should be enabled
|
||||
ship.setSlotEnabled(ship.cargoHatch, true)
|
||||
ship.setSlotEnabled(ship.cargoHatch, true);
|
||||
|
||||
// Largest possible refinery
|
||||
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1]
|
||||
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
|
||||
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.rf)
|
||||
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < refineryInternals.length; i++) {
|
||||
if (canMount(ship, refineryInternals[i], 'rf')) {
|
||||
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'))
|
||||
usedSlots.push(refineryInternals[i])
|
||||
break
|
||||
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
|
||||
usedSlots.push(refineryInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prospector limpet controller - 3A if possible
|
||||
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1]
|
||||
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
|
||||
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pc)
|
||||
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < prospectorInternals.length; i++) {
|
||||
if (canMount(ship, prospectorInternals[i], 'pc')) {
|
||||
// Prospector only has odd classes
|
||||
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass
|
||||
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'))
|
||||
usedSlots.push(prospectorInternals[i])
|
||||
break
|
||||
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass;
|
||||
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'));
|
||||
usedSlots.push(prospectorInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator if required
|
||||
if (shielded) {
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < shieldInternals.length; i++) {
|
||||
if (canMount(ship, shieldInternals[i], 'sg')) {
|
||||
ship.use(shieldInternals[i], sg)
|
||||
usedSlots.push(shieldInternals[i])
|
||||
break
|
||||
ship.use(shieldInternals[i], sg);
|
||||
usedSlots.push(shieldInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dual mining lasers of highest possible class; remove anything else
|
||||
const miningLaserOrder = [2, 3, 4, 1, 0]
|
||||
const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) {
|
||||
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass)
|
||||
})
|
||||
const miningLaserOrder = [2, 3, 4, 1, 0];
|
||||
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) {
|
||||
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
|
||||
});
|
||||
for (let s of miningLaserHardpoints) {
|
||||
if (s.maxClass >= 1 && miningLaserCount) {
|
||||
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'))
|
||||
miningLaserCount--
|
||||
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'));
|
||||
miningLaserCount--;
|
||||
} else {
|
||||
ship.use(s, null)
|
||||
ship.use(s, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
|
||||
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
|
||||
.reduce(function (a, b) {
|
||||
return a + b.m.getDps()
|
||||
}, 0)
|
||||
.reduce(function(a, b) {
|
||||
return a + b.m.getDps();
|
||||
}, 0);
|
||||
// Find out how many internal slots we have, and their potential cargo size
|
||||
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.cr)
|
||||
.map(b => Math.pow(2, b.maxClass))
|
||||
.map(b => Math.pow(2, b.maxClass));
|
||||
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
|
||||
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
|
||||
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1
|
||||
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)))
|
||||
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1;
|
||||
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
|
||||
|
||||
if (collectorLimpetsRequired > 0) {
|
||||
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.cc)
|
||||
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
|
||||
// Always keep at least 2 slots free for cargo racks (1 for shielded)
|
||||
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
|
||||
if (canMount(ship, collectorInternals[i], 'cc')) {
|
||||
// Collector only has odd classes
|
||||
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass
|
||||
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'))
|
||||
usedSlots.push(collectorInternals[i])
|
||||
collectorLimpetsRequired -= collectorInternals[i].m.maximum
|
||||
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
|
||||
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'));
|
||||
usedSlots.push(collectorInternals[i]);
|
||||
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power distributor to power the mining lasers indefinitely
|
||||
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
|
||||
.reduce(function (a, b) {
|
||||
return a + b.m.getEps()
|
||||
}, 0)
|
||||
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({weprate: wepRateRequired}).id
|
||||
.reduce(function(a, b) {
|
||||
return a + b.m.getEps();
|
||||
}, 0);
|
||||
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
|
||||
|
||||
// Fill the empty internals with cargo racks
|
||||
for (let i = ship.internal.length; i--;) {
|
||||
let slot = ship.internal[i]
|
||||
let slot = ship.internal[i];
|
||||
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'))
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
}
|
||||
|
||||
ship.useLightestStandard(standardOpts)
|
||||
ship.useLightestStandard(standardOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Racer Role
|
||||
* @param {Ship} ship Ship instance
|
||||
*/
|
||||
export function racer (ship) {
|
||||
export function racer(ship) {
|
||||
let standardOpts = {},
|
||||
usedSlots = [],
|
||||
sgSlot,
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass)
|
||||
usedSlots = [],
|
||||
sgSlot,
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
// Cargo hatch can be disabled
|
||||
ship.setSlotEnabled(ship.cargoHatch, false)
|
||||
ship.setSlotEnabled(ship.cargoHatch, false);
|
||||
|
||||
// Shield generator
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass))
|
||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < shieldInternals.length; i++) {
|
||||
if (canMount(ship, shieldInternals[i], 'sg')) {
|
||||
ship.use(shieldInternals[i], sg)
|
||||
usedSlots.push(shieldInternals[i])
|
||||
sgSlot = shieldInternals[i]
|
||||
break
|
||||
ship.use(shieldInternals[i], sg);
|
||||
usedSlots.push(shieldInternals[i]);
|
||||
sgSlot = shieldInternals[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty the hardpoints
|
||||
for (let s of ship.hardpoints) {
|
||||
ship.use(s, null)
|
||||
ship.use(s, null);
|
||||
}
|
||||
|
||||
// Empty the internals
|
||||
for (let i = ship.internal.length; i--;) {
|
||||
let slot = ship.internal[i]
|
||||
let slot = ship.internal[i];
|
||||
if (usedSlots.indexOf(slot) == -1) {
|
||||
ship.use(slot, null)
|
||||
ship.use(slot, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Best thrusters
|
||||
if (ship.standard[1].maxClass === 3) {
|
||||
standardOpts.th = 'tz'
|
||||
standardOpts.th = 'tz';
|
||||
} else if (ship.standard[1].maxClass === 2) {
|
||||
standardOpts.th = 'u0'
|
||||
standardOpts.th = 'u0';
|
||||
} else {
|
||||
standardOpts.th = ship.standard[1].maxClass + 'A'
|
||||
standardOpts.th = ship.standard[1].maxClass + 'A';
|
||||
}
|
||||
|
||||
// Best power distributor for more boosting
|
||||
standardOpts.pd = ship.standard[4].maxClass + 'A'
|
||||
standardOpts.pd = ship.standard[4].maxClass + 'A';
|
||||
|
||||
// Smallest possible FSD drive
|
||||
standardOpts.fsd = '2D'
|
||||
standardOpts.fsd = '2D';
|
||||
// Minimal fuel tank
|
||||
standardOpts.ft = '1C'
|
||||
standardOpts.ft = '1C';
|
||||
|
||||
// Disable nearly everything
|
||||
standardOpts.fsdDisabled = true
|
||||
standardOpts.sDisabled = true
|
||||
standardOpts.pdDisabled = true
|
||||
standardOpts.lsDisabled = true
|
||||
standardOpts.fsdDisabled = true;
|
||||
standardOpts.sDisabled = true;
|
||||
standardOpts.pdDisabled = true;
|
||||
standardOpts.lsDisabled = true;
|
||||
|
||||
ship.useLightestStandard(standardOpts)
|
||||
ship.useLightestStandard(standardOpts);
|
||||
|
||||
// Apply engineering to each module
|
||||
// ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]);
|
||||
@@ -414,4 +413,3 @@ export function racer (ship) {
|
||||
// ship.standard[5].m.blueprint.grade = 5;
|
||||
// setBest(ship, ship.standard[5].m);
|
||||
}
|
||||
|
||||
|
||||
83
src/app/shipyard/StatsFormatting.js
Normal file
83
src/app/shipyard/StatsFormatting.js
Normal file
@@ -0,0 +1,83 @@
|
||||
export const SI_PREFIXES = {
|
||||
'Y': 1e+24, // Yotta
|
||||
'Z': 1e+21, // Zetta
|
||||
'E': 1e+18, // Peta
|
||||
'P': 1e+15, // Peta
|
||||
'T': 1e+12, // Tera
|
||||
'G': 1e+9, // Giga
|
||||
'M': 1e+6, // Mega
|
||||
'k': 1e+3, // Kilo
|
||||
'h': 1e+2, // Hekto
|
||||
'da': 1e+1, // Deka
|
||||
'': 1,
|
||||
'd': 1e-1, // Dezi
|
||||
'c': 1e-2, // Zenti
|
||||
'm': 1e-3, // Milli
|
||||
'μ': 1e-6, // mikro not supported due to charset
|
||||
'n': 10e-9, // Nano
|
||||
'p': 1e-12, // Nano
|
||||
'f': 1e-15, // Femto
|
||||
'a': 1e-18, // Atto
|
||||
'z': 1e-21, // Zepto
|
||||
'y': 1e-24 // Yokto
|
||||
};
|
||||
|
||||
export const STATS_FORMATTING = {
|
||||
'ammo': { 'format': 'int', },
|
||||
'boot': { 'format': 'int', 'unit': 'secs' },
|
||||
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
||||
'burst': { 'format': 'int', 'change': 'additive' },
|
||||
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
|
||||
'causres': { 'format': 'pct' },
|
||||
'clip': { 'format': 'int' },
|
||||
'damage': { 'format': 'round' },
|
||||
'dps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDps' },
|
||||
'dpe': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDpe' },
|
||||
'distdraw': { 'format': 'round', 'unit': 'MW' },
|
||||
'duration': { 'format': 'round1', 'unit': 's' },
|
||||
'eff': { 'format': 'round2' },
|
||||
'engcap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'engrate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'eps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getEps' },
|
||||
'explres': { 'format': 'pct' },
|
||||
'facinglimit': { 'format': 'round1', 'unit': 'ang' },
|
||||
'falloff': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm' },
|
||||
'fallofffromrange': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm', 'synthetic': 'getFalloff' },
|
||||
'hps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getHps' },
|
||||
'hullboost': { 'format': 'pct1', 'change': 'additive' },
|
||||
'hullreinforcement': { 'format': 'int' },
|
||||
'integrity': { 'format': 'round1' },
|
||||
'jitter': { 'format': 'round', 'unit': 'ang' },
|
||||
'kinres': { 'format': 'pct' },
|
||||
'mass': { 'format': 'round1', 'unit': 'T' },
|
||||
'maxfuel': { 'format': 'round1', 'unit': 'T' },
|
||||
'optmass': { 'format': 'int', 'unit': 'T' },
|
||||
'optmul': { 'format': 'pct', 'change': 'additive' },
|
||||
'pgen': { 'format': 'round1', 'unit': 'MW' },
|
||||
'piercing': { 'format': 'int' },
|
||||
'power': { 'format': 'round', 'unit': 'MW' },
|
||||
'protection': { 'format': 'pct' },
|
||||
'range': { 'format': 'f2', 'unit': 'km', 'storedUnit': 'm' },
|
||||
'ranget': { 'format': 'f1', 'unit': 's' },
|
||||
'regen': { 'format': 'round1', 'unit': 'ps' },
|
||||
'reload': { 'format': 'int', 'unit': 's' },
|
||||
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
|
||||
'angle': { 'format': 'round1', 'unit': 'ang' },
|
||||
'scanrate': { 'format': 'int' },
|
||||
'scantime': { 'format': 'round1', 'unit': 's' },
|
||||
'sdps': { 'format': 'round1', 'units': 'ps', 'synthetic': 'getSDps' },
|
||||
'shield': { 'format': 'int', 'unit': 'MJ' },
|
||||
'shieldaddition': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'shieldboost': { 'format': 'pct1', 'change': 'additive' },
|
||||
'shieldreinforcement': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'shotspeed': { 'format': 'int', 'unit': 'm/s' },
|
||||
'spinup': { 'format': 'round1', 'unit': 's' },
|
||||
'syscap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'sysrate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'thermload': { 'format': 'round1' },
|
||||
'thermres': { 'format': 'pct' },
|
||||
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'weprate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'jumpboost': { 'format': 'round1', 'unit': 'LY' },
|
||||
'proberadius': { 'format': 'pct1', 'unit': 'pct' },
|
||||
};
|
||||
@@ -5,6 +5,7 @@ const LS_KEY_BUILDS = 'builds';
|
||||
const LS_KEY_COMPARISONS = 'comparisons';
|
||||
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
|
||||
const LS_KEY_COST_TAB = 'costTab';
|
||||
const LS_KEY_CMDR_NAME = 'cmdrName';
|
||||
const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
|
||||
const LS_KEY_INSURANCE = 'insurance';
|
||||
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
|
||||
@@ -13,6 +14,7 @@ const LS_KEY_STATE = 'state';
|
||||
const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
||||
const LS_KEY_ROLLS = 'matsPerGrade';
|
||||
|
||||
let LS;
|
||||
|
||||
@@ -67,7 +69,6 @@ function _delete(key) {
|
||||
* export is an instance (see end of this file).
|
||||
*/
|
||||
export class Persist extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Create an instance
|
||||
*/
|
||||
@@ -84,6 +85,8 @@ export class Persist extends EventEmitter {
|
||||
}
|
||||
|
||||
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
|
||||
let matsPerGrade = _get(LS_KEY_ROLLS);
|
||||
let cmdrName = _get(LS_KEY_CMDR_NAME);
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let insurance = _getString(LS_KEY_INSURANCE);
|
||||
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
|
||||
@@ -102,6 +105,14 @@ export class Persist extends EventEmitter {
|
||||
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
|
||||
this.state = _get(LS_KEY_STATE);
|
||||
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
|
||||
this.matsPerGrade = matsPerGrade || {
|
||||
1: 2,
|
||||
2: 2,
|
||||
3: 4,
|
||||
4: 4,
|
||||
5: 10
|
||||
};
|
||||
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
|
||||
this.tooltipsEnabled = tips === null ? true : tips;
|
||||
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
|
||||
|
||||
@@ -152,6 +163,10 @@ export class Persist extends EventEmitter {
|
||||
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
this.emit('moduleresistances', this.moduleResistancesEnabled);
|
||||
break;
|
||||
case LS_KEY_ROLLS:
|
||||
this.matsPerGrade = JSON.parse(newValue);
|
||||
this.emit('matsPerGrade', this.matsPerGrade);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// On JSON.Parse Error - don't sync or do anything
|
||||
@@ -457,6 +472,31 @@ export class Persist extends EventEmitter {
|
||||
return this.moduleDiscount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved ship discount
|
||||
* @param {Object} matsPerGrade # of rolls per grade
|
||||
*/
|
||||
setRolls(matsPerGrade) {
|
||||
this.matsPerGrade = matsPerGrade;
|
||||
_put(LS_KEY_ROLLS, this.matsPerGrade);
|
||||
this.emit('matsPerGrade');
|
||||
}
|
||||
/**
|
||||
* Get the saved Mats per grade
|
||||
* @return {Object} # of rolls per grade
|
||||
*/
|
||||
getRolls() {
|
||||
return this.matsPerGrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved Mats per grade
|
||||
* @return {Object} # of rolls per grade
|
||||
*/
|
||||
getCmdr() {
|
||||
return this.cmdrName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist selected cost tab
|
||||
* @param {number} tabName Cost tab name
|
||||
@@ -466,6 +506,16 @@ export class Persist extends EventEmitter {
|
||||
_put(LS_KEY_COST_TAB, tabName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist cmdr name
|
||||
* @param {Object} cmdrName Commander name for EDEngineer
|
||||
*/
|
||||
setCmdr(cmdrName) {
|
||||
this.cmdrName = cmdrName;
|
||||
_put(LS_KEY_CMDR_NAME, cmdrName);
|
||||
this.emit('cmdr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved discount
|
||||
* @return {number} val Discount value/amount
|
||||
|
||||
@@ -1,376 +1,435 @@
|
||||
import React from 'react';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's effects
|
||||
* @param {Object} translate The translate object
|
||||
* @param {Object} blueprint The blueprint at the required grade
|
||||
* @param {Array} engineers The engineers supplying this blueprint
|
||||
* @param {string} grp The group of the module
|
||||
* @param {Object} m The module to compare with
|
||||
* @returns {Object} The react components
|
||||
*/
|
||||
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
const effects = [];
|
||||
for (const feature in blueprint.features) {
|
||||
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (!featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let lowerBound = blueprint.features[feature][0];
|
||||
let upperBound = blueprint.features[feature][1];
|
||||
if (featureDef.type === 'percentage') {
|
||||
lowerBound = Math.round(lowerBound * 1000) / 10;
|
||||
upperBound = Math.round(upperBound * 1000) / 10;
|
||||
}
|
||||
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
|
||||
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
|
||||
if (m) {
|
||||
// We have a module - add in the current value
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
// We do not have a module, no value
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m) {
|
||||
// Because we have a module add in any benefits that aren't part of the primary blueprint
|
||||
for (const feature in m.mods) {
|
||||
if (!blueprint.features[feature]) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We also add in any benefits from specials that aren't covered above
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
|
||||
if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let components;
|
||||
if (!m) {
|
||||
components = [];
|
||||
for (const component in blueprint.components) {
|
||||
components.push(
|
||||
<tr key={component}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
|
||||
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let engineersList;
|
||||
if (engineers) {
|
||||
engineersList = [];
|
||||
for (const engineer of engineers) {
|
||||
engineersList.push(
|
||||
<tr key={engineer}>
|
||||
<td style={{ textAlign: 'left' }}>{engineer}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('feature')}</td>
|
||||
<td>{translate('worst')}</td>
|
||||
{m ? <td>{translate('current')}</td> : null }
|
||||
<td>{translate('best')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{effects}
|
||||
</tbody>
|
||||
</table>
|
||||
{ components ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('component')}</td>
|
||||
<td>{translate('amount')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{components}
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ engineersList ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('engineers')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{engineersList}
|
||||
</tbody>
|
||||
</table> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this blueprint feature beneficial?
|
||||
* @param {string} feature The name of the feature
|
||||
* @param {array} values The value of the feature
|
||||
* @returns {boolean} True if this feature is beneficial
|
||||
*/
|
||||
export function isBeneficial(feature, values) {
|
||||
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
|
||||
if (Modifications.modifications[feature].higherbetter) {
|
||||
return !fact;
|
||||
} else {
|
||||
return fact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this feature value beneficial?
|
||||
* @param {string} feature The name of the feature
|
||||
* @param {number} value The value of the feature
|
||||
* @returns {boolean} True if this value is beneficial
|
||||
*/
|
||||
export function isValueBeneficial(feature, value) {
|
||||
if (Modifications.modifications[feature].higherbetter) {
|
||||
return value > 0;
|
||||
} else {
|
||||
return value < 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a blueprint with a given name and an optional module
|
||||
* @param {string} name The name of the blueprint
|
||||
* @param {Object} module The module for which to obtain this blueprint
|
||||
* @returns {Object} The matching blueprint
|
||||
*/
|
||||
export function getBlueprint(name, module) {
|
||||
// Start with a copy of the blueprint
|
||||
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0)
|
||||
const found = Modifications.blueprints[findMod(name)];
|
||||
if (!found || !found.fdname) {
|
||||
return undefined;
|
||||
}
|
||||
const blueprint = JSON.parse(JSON.stringify(found));
|
||||
if (module) {
|
||||
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
|
||||
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
|
||||
for (const grade in blueprint.grades) {
|
||||
for (const feature in blueprint.grades[grade].features) {
|
||||
if (feature === 'explres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
|
||||
}
|
||||
if (feature === 'kinres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
|
||||
}
|
||||
if (feature === 'thermres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (module.grp === 'sb') {
|
||||
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
|
||||
// being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
|
||||
// accurate as per the information in Elite
|
||||
for (const grade in blueprint.grades) {
|
||||
for (const feature in blueprint.grades[grade].features) {
|
||||
if (feature === 'shieldboost') {
|
||||
blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
|
||||
blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'worst' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
*/
|
||||
export function setWorst(ship, m) {
|
||||
ship.clearModifications(m);
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
const value = features[featureName][0];
|
||||
_setValue(ship, m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'best' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
*/
|
||||
export function setBest(ship, m) {
|
||||
ship.clearModifications(m);
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
const value = features[featureName][1];
|
||||
_setValue(ship, m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'extreme' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
*/
|
||||
export function setExtreme(ship, m) {
|
||||
ship.clearModifications(m);
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][0];
|
||||
} else {
|
||||
value = features[featureName][1];
|
||||
}
|
||||
} else {
|
||||
// Higher is worse, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][1];
|
||||
} else {
|
||||
value = features[featureName][0];
|
||||
}
|
||||
}
|
||||
|
||||
_setValue(ship, m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'random' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
*/
|
||||
export function setRandom(ship, m) {
|
||||
ship.clearModifications(m);
|
||||
// Pick a single value for our randomness
|
||||
const mult = Math.random();
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
||||
} else {
|
||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
||||
}
|
||||
} else {
|
||||
// Higher is worse, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
||||
} else {
|
||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
||||
}
|
||||
}
|
||||
|
||||
_setValue(ship, m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a modification feature value
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
* @param {string} featureName The feature being set
|
||||
* @param {number} value The value being set for the feature
|
||||
*/
|
||||
function _setValue(ship, m, featureName, value) {
|
||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
||||
ship.setModification(m, featureName, value * 10000);
|
||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
||||
ship.setModification(m, featureName, value * 100);
|
||||
} else {
|
||||
ship.setModification(m, featureName, value);
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { STATS_FORMATTING } from '../shipyard/StatsFormatting';
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's specials
|
||||
* @param {Object} translate The translate object
|
||||
* @param {Object} blueprint The blueprint at the required grade
|
||||
* @param {string} grp The group of the module
|
||||
* @param {Object} m The module to compare with
|
||||
* @param {string} specialName The name of the special
|
||||
* @returns {Object} The react components
|
||||
*/
|
||||
export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
||||
const effects = [];
|
||||
if (!blueprint || !blueprint.features) {
|
||||
return undefined;
|
||||
}
|
||||
if (m) {
|
||||
// We also add in any benefits from specials that aren't covered above
|
||||
if (m.blueprint) {
|
||||
for (const feature in Modifications.modifierActions[specialName]) {
|
||||
// if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature) - m.getModValue(feature, true);
|
||||
if (featureDef.type === 'percentage') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
|
||||
effects.push(
|
||||
<tr key={feature + '_specialTT'}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
||||
style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<tbody>
|
||||
{effects}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's effects
|
||||
* @param {Object} translate The translate object
|
||||
* @param {Object} blueprint The blueprint at the required grade
|
||||
* @param {Array} engineers The engineers supplying this blueprint
|
||||
* @param {string} grp The group of the module
|
||||
* @param {Object} m The module to compare with
|
||||
* @returns {Object} The react components
|
||||
*/
|
||||
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
const effects = [];
|
||||
if (!blueprint || !blueprint.features) {
|
||||
return undefined;
|
||||
}
|
||||
for (const feature in blueprint.features) {
|
||||
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (!featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let lowerBound = blueprint.features[feature][0];
|
||||
let upperBound = blueprint.features[feature][1];
|
||||
if (featureDef.type === 'percentage') {
|
||||
lowerBound = Math.round(lowerBound * 1000) / 10;
|
||||
upperBound = Math.round(upperBound * 1000) / 10;
|
||||
}
|
||||
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
|
||||
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
|
||||
if (m) {
|
||||
// We have a module - add in the current value
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
// We do not have a module, no value
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m) {
|
||||
// Because we have a module add in any benefits that aren't part of the primary blueprint
|
||||
for (const feature in m.mods) {
|
||||
if (!blueprint.features[feature]) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We also add in any benefits from specials that aren't covered above
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
|
||||
if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let components;
|
||||
if (!m) {
|
||||
components = [];
|
||||
for (const component in blueprint.components) {
|
||||
components.push(
|
||||
<tr key={component}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
|
||||
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let engineersList;
|
||||
if (engineers) {
|
||||
engineersList = [];
|
||||
for (const engineer of engineers) {
|
||||
engineersList.push(
|
||||
<tr key={engineer}>
|
||||
<td style={{ textAlign: 'left' }}>{engineer}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('feature')}</td>
|
||||
<td>{translate('worst')}</td>
|
||||
{m ? <td>{translate('current')}</td> : null }
|
||||
<td>{translate('best')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{effects}
|
||||
</tbody>
|
||||
</table>
|
||||
{ components ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('component')}</td>
|
||||
<td>{translate('amount')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{components}
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ engineersList ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('engineers')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{engineersList}
|
||||
</tbody>
|
||||
</table> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this blueprint feature beneficial?
|
||||
* @param {string} feature The name of the feature
|
||||
* @param {array} values The value of the feature
|
||||
* @returns {boolean} True if this feature is beneficial
|
||||
*/
|
||||
export function isBeneficial(feature, values) {
|
||||
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
|
||||
if (Modifications.modifications[feature].higherbetter) {
|
||||
return !fact;
|
||||
} else {
|
||||
return fact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this feature value beneficial?
|
||||
* @param {string} feature The name of the feature
|
||||
* @param {number} value The value of the feature
|
||||
* @returns {boolean} True if this value is beneficial
|
||||
*/
|
||||
export function isValueBeneficial(feature, value) {
|
||||
if (Modifications.modifications[feature].higherbetter) {
|
||||
return value > 0;
|
||||
} else {
|
||||
return value < 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the change as shown beneficial?
|
||||
* @param {string} feature The name of the feature
|
||||
* @param {number} value The value of the feature as percentage change
|
||||
* @returns True if the value is beneficial
|
||||
*/
|
||||
export function isChangeValueBeneficial(feature, value) {
|
||||
let changeHigherBetter = STATS_FORMATTING[feature].higherbetter;
|
||||
if (changeHigherBetter === undefined) {
|
||||
return isValueBeneficial(feature, value);
|
||||
}
|
||||
|
||||
if (changeHigherBetter) {
|
||||
return value > 0;
|
||||
} else {
|
||||
return value < 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a blueprint with a given name and an optional module
|
||||
* @param {string} name The name of the blueprint
|
||||
* @param {Object} module The module for which to obtain this blueprint
|
||||
* @returns {Object} The matching blueprint
|
||||
*/
|
||||
export function getBlueprint(name, module) {
|
||||
// Start with a copy of the blueprint
|
||||
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
|
||||
const found = Modifications.blueprints[findMod(name)];
|
||||
if (!found || !found.fdname) {
|
||||
return {};
|
||||
}
|
||||
const blueprint = JSON.parse(JSON.stringify(found));
|
||||
return blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'percent' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
* @param {Number} percent The percent to set values to of full.
|
||||
*/
|
||||
export function setPercent(ship, m, percent) {
|
||||
ship.clearModifications(m);
|
||||
// Pick given value as multiplier
|
||||
const mult = percent / 100;
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
||||
} else {
|
||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
||||
}
|
||||
} else {
|
||||
// Higher is worse, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
|
||||
} else {
|
||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
|
||||
}
|
||||
}
|
||||
|
||||
_setValue(ship, m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'random' primary modifications
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
*/
|
||||
export function setRandom(ship, m) {
|
||||
// Pick a single value for our randomness
|
||||
setPercent(ship, m, Math.random() * 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a modification feature value
|
||||
* @param {Object} ship The ship for which to perform the modifications
|
||||
* @param {Object} m The module for which to perform the modifications
|
||||
* @param {string} featureName The feature being set
|
||||
* @param {number} value The value being set for the feature
|
||||
*/
|
||||
function _setValue(ship, m, featureName, value) {
|
||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
||||
ship.setModification(m, featureName, value * 10000);
|
||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
||||
ship.setModification(m, featureName, value * 100);
|
||||
} else {
|
||||
ship.setModification(m, featureName, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide 'percent' primary query
|
||||
* @param {Object} m The module for which to perform the query
|
||||
* @returns {Number} percent The percentage indicator of current applied values.
|
||||
*/
|
||||
export function getPercent(m) {
|
||||
let result = null;
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
if (features[featureName][0] === features[featureName][1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = _getValue(m, featureName);
|
||||
let mult;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
|
||||
} else {
|
||||
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
|
||||
}
|
||||
} else {
|
||||
// Higher is worse, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
|
||||
} else {
|
||||
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
if (result && result != mult) {
|
||||
return null;
|
||||
} else if (result != mult) {
|
||||
result = mult;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query a feature value
|
||||
* @param {Object} m The module for which to perform the query
|
||||
* @param {string} featureName The feature being queried
|
||||
* @returns {number} The value of the modification as a %
|
||||
*/
|
||||
function _getValue(m, featureName) {
|
||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
||||
return m.getModValue(featureName, true) / 10000;
|
||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
||||
return m.getModValue(featureName, true) / 100;
|
||||
} else {
|
||||
return m.getModValue(featureName, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getBlueprint } from '../utils/BlueprintFunctions';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
|
||||
// mapping from fd's ship model names to coriolis'
|
||||
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Adder': 'adder',
|
||||
'Anaconda': 'anaconda',
|
||||
'Asp': 'asp',
|
||||
@@ -29,6 +29,9 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'FerDeLance': 'fer_de_lance',
|
||||
'Hauler': 'hauler',
|
||||
'Independant_Trader': 'keelback',
|
||||
'Krait_MkII': 'krait_mkii',
|
||||
'Mamba': 'mamba',
|
||||
'Krait_Light': 'krait_phantom',
|
||||
'Orca': 'orca',
|
||||
'Python': 'python',
|
||||
'SideWinder': 'sidewinder',
|
||||
@@ -37,6 +40,8 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Type9': 'type_9_heavy',
|
||||
'Type9_Military': 'type_10_defender',
|
||||
'TypeX': 'alliance_chieftain',
|
||||
'TypeX_2': 'alliance_crusader',
|
||||
'TypeX_3': 'alliance_challenger',
|
||||
'Viper': 'viper',
|
||||
'Viper_MkIV': 'viper_mk_iv',
|
||||
'Vulture': 'vulture'
|
||||
@@ -148,15 +153,15 @@ export function shipFromJson(json) {
|
||||
|
||||
// Add the bulkheads
|
||||
const armourJson = json.modules.Armour.module;
|
||||
if (armourJson.name.endsWith('_Armour_Grade1')) {
|
||||
if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) {
|
||||
ship.useBulkhead(0, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade2')) {
|
||||
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) {
|
||||
ship.useBulkhead(1, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade3')) {
|
||||
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) {
|
||||
ship.useBulkhead(2, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Mirrored')) {
|
||||
} else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) {
|
||||
ship.useBulkhead(3, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Reactive')) {
|
||||
} else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) {
|
||||
ship.useBulkhead(4, true);
|
||||
} else {
|
||||
throw 'Unknown bulkheads "' + armourJson.name + '"';
|
||||
@@ -318,7 +323,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
||||
if (!modifiers) return;
|
||||
let special;
|
||||
if (specialModifications) {
|
||||
special = Modifications.specials[Object.keys(specialModifications)[0]]
|
||||
special = Modifications.specials[Object.keys(specialModifications)[0]];
|
||||
}
|
||||
for (const i in modifiers) {
|
||||
// Some special modifications
|
||||
@@ -345,7 +350,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
||||
let value;
|
||||
if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') {
|
||||
value = modifiers[i].value - 1;
|
||||
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
|
||||
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
|
||||
value = (modifiers[i].value - module.hullboost) / module.hullboost;
|
||||
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') {
|
||||
value = modifiers[i].value / module.hullboost;
|
||||
|
||||
@@ -1,292 +1,303 @@
|
||||
import Ship from '../shipyard/Ship'
|
||||
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils'
|
||||
import { Ships } from 'coriolis-data/dist'
|
||||
import Module from '../shipyard/Module'
|
||||
import { Modules } from 'coriolis-data/dist'
|
||||
import { Modifications } from 'coriolis-data/dist'
|
||||
import { getBlueprint } from './BlueprintFunctions'
|
||||
|
||||
/**
|
||||
* Obtain a module given its FD Name
|
||||
* @param {string} fdname the FD Name of the module
|
||||
* @return {Module} the module
|
||||
*/
|
||||
function _moduleFromFdName (fdname) {
|
||||
if (!fdname) return null
|
||||
fdname = fdname.toLowerCase()
|
||||
// Check standard modules
|
||||
for (const grp in Modules.standard) {
|
||||
if (Modules.standard.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.standard[grp]) {
|
||||
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({template: Modules.standard[grp][i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check hardpoint modules
|
||||
for (const grp in Modules.hardpoints) {
|
||||
if (Modules.hardpoints.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.hardpoints[grp]) {
|
||||
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({template: Modules.hardpoints[grp][i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check internal modules
|
||||
for (const grp in Modules.internal) {
|
||||
if (Modules.internal.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.internal[grp]) {
|
||||
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({template: Modules.internal[grp][i]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a ship from the journal Loadout event JSON
|
||||
* @param {object} json the Loadout event JSON
|
||||
* @return {Ship} the built ship
|
||||
*/
|
||||
export function shipFromLoadoutJSON (json) {
|
||||
// Start off building a basic ship
|
||||
const shipModel = shipModelFromJson(json)
|
||||
if (!shipModel) {
|
||||
throw 'No such ship found: "' + json.Ship + '"'
|
||||
}
|
||||
const shipTemplate = Ships[shipModel]
|
||||
|
||||
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots)
|
||||
ship.buildWith(null)
|
||||
// Initial Ship building, don't do engineering yet.
|
||||
let opts = [];
|
||||
|
||||
for (const module of json.Modules) {
|
||||
switch (module.Slot) {
|
||||
// Cargo Hatch.
|
||||
case 'CargoHatch':
|
||||
ship.cargoHatch.enabled = module.On
|
||||
ship.cargoHatch.priority = module.Priority
|
||||
break
|
||||
// Add the bulkheads
|
||||
case 'Armour':
|
||||
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
|
||||
ship.useBulkhead(0, true)
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
|
||||
ship.useBulkhead(1, true)
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
|
||||
ship.useBulkhead(2, true)
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
|
||||
ship.useBulkhead(3, true)
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
|
||||
ship.useBulkhead(4, true)
|
||||
} else {
|
||||
throw 'Unknown bulkheads "' + module.Item + '"'
|
||||
}
|
||||
ship.bulkheads.enabled = true
|
||||
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'PowerPlant':
|
||||
const powerplant = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[0], powerplant, true)
|
||||
ship.standard[0].enabled = module.On
|
||||
ship.standard[0].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'MainEngines':
|
||||
const thrusters = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[1], thrusters, true)
|
||||
ship.standard[1].enabled = module.On
|
||||
ship.standard[1].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'FrameShiftDrive':
|
||||
const frameshiftdrive = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[2], frameshiftdrive, true)
|
||||
ship.standard[2].enabled = module.On
|
||||
ship.standard[2].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'LifeSupport':
|
||||
const lifesupport = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[3], lifesupport, true)
|
||||
ship.standard[3].enabled = module.On === true
|
||||
ship.standard[3].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'PowerDistributor':
|
||||
const powerdistributor = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[4], powerdistributor, true)
|
||||
ship.standard[4].enabled = module.On
|
||||
ship.standard[4].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'Radar':
|
||||
const sensors = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[5], sensors, true)
|
||||
ship.standard[5].enabled = module.On
|
||||
ship.standard[5].priority = module.Priority
|
||||
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level)
|
||||
break
|
||||
case 'FuelTank':
|
||||
const fueltank = _moduleFromFdName(module.Item)
|
||||
ship.use(ship.standard[6], fueltank, true)
|
||||
ship.standard[6].enabled = true
|
||||
ship.standard[6].priority = 0
|
||||
break
|
||||
default:
|
||||
}
|
||||
for (const module of json.Modules) {
|
||||
if (module.Slot.search(/Hardpoint/) !== -1) {
|
||||
// Add hardpoints
|
||||
let hardpoint;
|
||||
let hardpointClassNum = -1
|
||||
let hardpointSlotNum = -1
|
||||
let hardpointArrayNum = 0
|
||||
for (let i in shipTemplate.slots.hardpoints) {
|
||||
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
|
||||
// Another slot of the same class
|
||||
hardpointSlotNum++
|
||||
} else {
|
||||
// The first slot of a new class
|
||||
hardpointClassNum = shipTemplate.slots.hardpoints[i]
|
||||
hardpointSlotNum = 1
|
||||
}
|
||||
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot === hardpointName)
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot) {
|
||||
// No module
|
||||
} else {
|
||||
hardpoint = _moduleFromFdName(hardpointSlot.Item)
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true)
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority
|
||||
opts.push({coriolisMod: hardpoint, json: hardpointSlot});
|
||||
}
|
||||
hardpointArrayNum++
|
||||
}
|
||||
}
|
||||
if (module.Slot.search(/Slot\d/) !== -1) {
|
||||
let internalSlotNum = 1
|
||||
let militarySlotNum = 1
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'Military' : false
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum
|
||||
internalSlot = json.Modules.find(elem => elem.Slot === internalName)
|
||||
militarySlotNum++
|
||||
} else {
|
||||
// Slot numbers are not contiguous so handle skips.
|
||||
while (internalSlot === null && internalSlotNum < 99) {
|
||||
// Slot sizes have no relationship to the actual size, either, so check all possibilities
|
||||
for (let slotsize = 0; slotsize < 9; slotsize++) {
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize
|
||||
if (json.Modules.find(elem => elem.Slot === internalName)) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot === internalName);
|
||||
break
|
||||
}
|
||||
}
|
||||
internalSlotNum++
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else if (!internalSlot) {
|
||||
// No module
|
||||
} else {
|
||||
const internalJson = internalSlot
|
||||
const internal = _moduleFromFdName(internalJson.Item)
|
||||
ship.use(ship.internal[i], internal, true)
|
||||
ship.internal[i].enabled = internalJson.On === true
|
||||
ship.internal[i].priority = internalJson.Priority
|
||||
opts.push({coriolisMod: internal, json: internalSlot});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of opts) {
|
||||
if (i.json.Engineering) _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect)
|
||||
}
|
||||
// We don't have any information on it so guess it's priority 5 and disabled
|
||||
if (!ship.cargoHatch) {
|
||||
ship.cargoHatch.enabled = false
|
||||
ship.cargoHatch.priority = 4
|
||||
}
|
||||
console.log(ship)
|
||||
|
||||
// Now update the ship's codes before returning it
|
||||
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the modifications for a module
|
||||
* @param {Module} module the module
|
||||
* @param {Object} modifiers the modifiers
|
||||
* @param {Object} blueprint the blueprint of the modification
|
||||
* @param {Object} grade the grade of the modification
|
||||
* @param {Object} specialModifications special modification
|
||||
*/
|
||||
function _addModifications (module, modifiers, blueprint, grade, specialModifications) {
|
||||
if (!modifiers) return
|
||||
let special
|
||||
if (specialModifications) {
|
||||
special = Modifications.specials[Object.keys(specialModifications)[0]]
|
||||
}
|
||||
for (const i in modifiers) {
|
||||
// Some special modifications
|
||||
// Look up the modifiers to find what we need to do
|
||||
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0)
|
||||
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)]
|
||||
//TODO: Figure out how to scale this value.
|
||||
if (!!modifiers[i].LessIsGood) {
|
||||
|
||||
}
|
||||
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
|
||||
if (value === Infinity) {
|
||||
value = modifiers[i].Value * 100;
|
||||
}
|
||||
if (modifiers[i].Label === 'DefenceModifierHealthMultiplier' && blueprint.toLowerCase().startsWith('armour_')) {
|
||||
value = modifiers[i].Value * 100 - modifiers[i].OriginalValue;
|
||||
}
|
||||
// Carry out the required changes
|
||||
for (const action in modifierActions) {
|
||||
if (isNaN(modifierActions[action])) {
|
||||
module.setModValue(action, modifierActions[action])
|
||||
} else {
|
||||
module.setModValue(action, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the blueprint definition, grade and special
|
||||
if (blueprint) {
|
||||
module.blueprint = getBlueprint(blueprint, module)
|
||||
if (grade) {
|
||||
module.blueprint.grade = Number(grade)
|
||||
}
|
||||
if (special) {
|
||||
module.blueprint.special = special
|
||||
}
|
||||
}
|
||||
}
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Module from '../shipyard/Module';
|
||||
import { Modules } from 'coriolis-data/dist';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { getBlueprint } from './BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Obtain a module given its FD Name
|
||||
* @param {string} fdname the FD Name of the module
|
||||
* @return {Module} the module
|
||||
*/
|
||||
function _moduleFromFdName(fdname) {
|
||||
if (!fdname) return null;
|
||||
fdname = fdname.toLowerCase();
|
||||
// Check standard modules
|
||||
for (const grp in Modules.standard) {
|
||||
if (Modules.standard.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.standard[grp]) {
|
||||
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.standard[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check hardpoint modules
|
||||
for (const grp in Modules.hardpoints) {
|
||||
if (Modules.hardpoints.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.hardpoints[grp]) {
|
||||
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.hardpoints[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check internal modules
|
||||
for (const grp in Modules.internal) {
|
||||
if (Modules.internal.hasOwnProperty(grp)) {
|
||||
for (const i in Modules.internal[grp]) {
|
||||
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.internal[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a ship from the journal Loadout event JSON
|
||||
* @param {object} json the Loadout event JSON
|
||||
* @return {Ship} the built ship
|
||||
*/
|
||||
export function shipFromLoadoutJSON(json) {
|
||||
// Start off building a basic ship
|
||||
const shipModel = shipModelFromJson(json);
|
||||
if (!shipModel) {
|
||||
throw 'No such ship found: "' + json.Ship + '"';
|
||||
}
|
||||
const shipTemplate = Ships[shipModel];
|
||||
|
||||
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||
ship.buildWith(null);
|
||||
// Initial Ship building, don't do engineering yet.
|
||||
let modsToAdd = [];
|
||||
|
||||
for (const module of json.Modules) {
|
||||
switch (module.Slot.toLowerCase()) {
|
||||
// Cargo Hatch.
|
||||
case 'cargohatch':
|
||||
ship.cargoHatch.enabled = module.On;
|
||||
ship.cargoHatch.priority = module.Priority;
|
||||
break;
|
||||
// Add the bulkheads
|
||||
case 'armour':
|
||||
if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
|
||||
ship.useBulkhead(0, true);
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
|
||||
ship.useBulkhead(1, true);
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
|
||||
ship.useBulkhead(2, true);
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
|
||||
ship.useBulkhead(3, true);
|
||||
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
|
||||
ship.useBulkhead(4, true);
|
||||
} else {
|
||||
throw 'Unknown bulkheads "' + module.Item + '"';
|
||||
}
|
||||
ship.bulkheads.enabled = true;
|
||||
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'powerplant':
|
||||
const powerplant = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[0], powerplant, true);
|
||||
ship.standard[0].enabled = module.On;
|
||||
ship.standard[0].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'mainengines':
|
||||
const thrusters = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[1], thrusters, true);
|
||||
ship.standard[1].enabled = module.On;
|
||||
ship.standard[1].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'frameshiftdrive':
|
||||
const frameshiftdrive = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[2], frameshiftdrive, true);
|
||||
ship.standard[2].enabled = module.On;
|
||||
ship.standard[2].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'lifesupport':
|
||||
const lifesupport = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[3], lifesupport, true);
|
||||
ship.standard[3].enabled = module.On === true;
|
||||
ship.standard[3].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'powerdistributor':
|
||||
const powerdistributor = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[4], powerdistributor, true);
|
||||
ship.standard[4].enabled = module.On;
|
||||
ship.standard[4].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'radar':
|
||||
const sensors = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[5], sensors, true);
|
||||
ship.standard[5].enabled = module.On;
|
||||
ship.standard[5].priority = module.Priority;
|
||||
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||
break;
|
||||
case 'fueltank':
|
||||
const fueltank = _moduleFromFdName(module.Item);
|
||||
ship.use(ship.standard[6], fueltank, true);
|
||||
ship.standard[6].enabled = true;
|
||||
ship.standard[6].priority = 0;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
|
||||
// Add hardpoints
|
||||
let hardpoint;
|
||||
let hardpointClassNum = -1;
|
||||
let hardpointSlotNum = -1;
|
||||
let hardpointArrayNum = 0;
|
||||
for (let i in shipTemplate.slots.hardpoints) {
|
||||
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
|
||||
// Another slot of the same class
|
||||
hardpointSlotNum++;
|
||||
} else {
|
||||
// The first slot of a new class
|
||||
hardpointClassNum = shipTemplate.slots.hardpoints[i];
|
||||
hardpointSlotNum = 1;
|
||||
}
|
||||
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot) {
|
||||
// No module
|
||||
} else {
|
||||
hardpoint = _moduleFromFdName(hardpointSlot.Item);
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
||||
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
|
||||
}
|
||||
hardpointArrayNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let internalSlotNum = 0;
|
||||
let militarySlotNum = 1;
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null;
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum;
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
militarySlotNum++;
|
||||
} else {
|
||||
// Slot numbers are not contiguous so handle skips.
|
||||
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
|
||||
// Slot sizes have no relationship to the actual size, either, so check all possibilities
|
||||
for (let slotsize = 0; slotsize < 9; slotsize++) {
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
|
||||
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else {
|
||||
const internalJson = internalSlot;
|
||||
const internal = _moduleFromFdName(internalJson.Item);
|
||||
ship.use(ship.internal[i], internal, true);
|
||||
ship.internal[i].enabled = internalJson.On === true;
|
||||
ship.internal[i].priority = internalJson.Priority;
|
||||
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of modsToAdd) {
|
||||
if (i.json.Engineering) {
|
||||
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
|
||||
}
|
||||
}
|
||||
// We don't have any information on it so guess it's priority 5 and disabled
|
||||
if (!ship.cargoHatch) {
|
||||
ship.cargoHatch.enabled = false;
|
||||
ship.cargoHatch.priority = 4;
|
||||
}
|
||||
|
||||
// Now update the ship's codes before returning it
|
||||
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the modifications for a module
|
||||
* @param {Module} module the module
|
||||
* @param {Object} modifiers the modifiers
|
||||
* @param {Object} blueprint the blueprint of the modification
|
||||
* @param {Object} grade the grade of the modification
|
||||
* @param {Object} specialModifications special modification
|
||||
*/
|
||||
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
|
||||
if (!modifiers) return;
|
||||
let special;
|
||||
if (specialModifications) {
|
||||
if (specialModifications == 'special_plasma_slug') {
|
||||
if (module.symbol.match(/PlasmaAccelerator/i)) {
|
||||
specialModifications = 'special_plasma_slug_pa';
|
||||
} else {
|
||||
specialModifications = 'special_plasma_slug_cooled';
|
||||
}
|
||||
}
|
||||
special = Modifications.specials[specialModifications];
|
||||
}
|
||||
// Add the blueprint definition, grade and special
|
||||
if (blueprint) {
|
||||
module.blueprint = getBlueprint(blueprint, module);
|
||||
if (grade) {
|
||||
module.blueprint.grade = Number(grade);
|
||||
}
|
||||
if (special) {
|
||||
module.blueprint.special = special;
|
||||
}
|
||||
}
|
||||
for (const i in modifiers) {
|
||||
// Some special modifications
|
||||
// Look up the modifiers to find what we need to do
|
||||
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
|
||||
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
|
||||
// TODO: Figure out how to scale this value.
|
||||
if (!!modifiers[i].LessIsGood) {
|
||||
|
||||
}
|
||||
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
|
||||
if (value === Infinity) {
|
||||
value = modifiers[i].Value * 100;
|
||||
}
|
||||
if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
|
||||
value = (modifiers[i].Value / module.range - 1) * 100;
|
||||
}
|
||||
if (modifiers[i].Label.search('Resistance') >= 0) {
|
||||
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
|
||||
}
|
||||
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
|
||||
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
|
||||
}
|
||||
|
||||
// Carry out the required changes
|
||||
for (const action in modifierActions) {
|
||||
if (isNaN(modifierActions[action])) {
|
||||
module.setModValue(action, modifierActions[action]);
|
||||
} else {
|
||||
module.setModValue(action, value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import request from 'superagent';
|
||||
|
||||
let agent;
|
||||
try {
|
||||
agent = request.agent(); // apparently this crashes somehow
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (!agent) {
|
||||
agent = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten a URL
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
*/
|
||||
export default function shorternUrl(url, success, error) {
|
||||
shortenUrlEddp(url, success, error);
|
||||
orbisShorten(url, success, error);
|
||||
}
|
||||
|
||||
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
@@ -64,3 +73,68 @@ function shortenUrlEddp(url, success, error) {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const SHORTEN_API_ORBIS = 'https://s.orbis.zone/api.php';
|
||||
/**
|
||||
* Shorten a URL using Orbis's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
function orbisShorten(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API_ORBIS)
|
||||
.field('action', 'shorturl')
|
||||
.field('url', url)
|
||||
.field('format', 'json')
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
error('Bad Request');
|
||||
} else {
|
||||
success(response.body.shorturl);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
error(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const API_ORBIS = 'https://api.orbis.zone/ships';
|
||||
/**
|
||||
* Upload to Orbis
|
||||
* @param {object} ship The URL to shorten
|
||||
* @param {object} creds Orbis credentials
|
||||
* @return {Promise<any>} Either a URL or error message.
|
||||
*/
|
||||
export function orbisUpload(ship, creds) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
agent
|
||||
.post(API_ORBIS)
|
||||
.withCredentials()
|
||||
.redirects(0)
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(ship)
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
reject('Bad Request');
|
||||
} else {
|
||||
resolve(response.body.link);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
reject(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
reject('Not Online');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import Module from '../shipyard/Module';
|
||||
import { Infinite } from '../components/SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
|
||||
/**
|
||||
* Determine if a slot on a ship can mount a module of a particular class and group
|
||||
@@ -142,20 +140,21 @@ function diff(format, mVal, mmVal) {
|
||||
export function diffDetails(language, m, mm) {
|
||||
let { formats, translate, units } = language;
|
||||
let propDiffs = [];
|
||||
m = new Module(m);
|
||||
|
||||
// Module-specific items
|
||||
|
||||
if (m.grp === 'pp') {
|
||||
let mPowerGeneration = m.pgen || 0;
|
||||
let mPowerGeneration = m.getPowerGeneration() || 0;
|
||||
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
|
||||
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
|
||||
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
|
||||
} else {
|
||||
let mPowerUsage = m.power || 0;
|
||||
let mPowerUsage = m.getPowerUsage() || 0;
|
||||
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
|
||||
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
|
||||
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
|
||||
}
|
||||
|
||||
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
|
||||
let mDps = m.getDps() || 0;
|
||||
let mmDps = mm ? mm.getDps() || 0 : 0;
|
||||
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
|
||||
|
||||
@@ -167,7 +166,7 @@ export function diffDetails(language, m, mm) {
|
||||
|
||||
if (mAffectsShield) {
|
||||
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
|
||||
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
|
||||
newShield = this.calcShieldStrengthWith(null, m.getShieldBoost() - (mm ? mm.getShieldBoost() || 0 : 0));
|
||||
} else {
|
||||
newShield = this.calcShieldStrengthWith(m);
|
||||
}
|
||||
@@ -182,7 +181,7 @@ export function diffDetails(language, m, mm) {
|
||||
}
|
||||
|
||||
if (m.grp === 'mrp') {
|
||||
let mProtection = m.protection;
|
||||
let mProtection = m.getProtection();
|
||||
let mmProtection = mm ? mm.getProtection() || 0 : 0;
|
||||
if (mProtection != mmProtection) {
|
||||
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
|
||||
@@ -190,7 +189,7 @@ export function diffDetails(language, m, mm) {
|
||||
}
|
||||
|
||||
if (m.grp === 'hr') {
|
||||
let mHullReinforcement = m.hullreinforcement;
|
||||
let mHullReinforcement = m.getHullReinforcement();
|
||||
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
|
||||
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
|
||||
}
|
||||
@@ -222,7 +221,7 @@ export function diffDetails(language, m, mm) {
|
||||
let mmCost = mm ? mm.cost : 0;
|
||||
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
|
||||
|
||||
let mMass = m.mass || 0;
|
||||
let mMass = m.getMass() || 0;
|
||||
let mmMass = mm ? mm.getMass() : 0;
|
||||
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
||||
|
||||
@@ -243,7 +242,7 @@ export function diffDetails(language, m, mm) {
|
||||
}
|
||||
}
|
||||
|
||||
let mIntegrity = m.integrity || 0;
|
||||
let mIntegrity = m.getIntegrity() || 0;
|
||||
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
|
||||
if (mIntegrity != mmIntegrity) {
|
||||
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);
|
||||
|
||||
9
src/iframe.html
Normal file
9
src/iframe.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="xdLocalStoragePostMessageApi.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
This is the magical iframe
|
||||
</body>
|
||||
</html>
|
||||
@@ -27,7 +27,7 @@
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "https:\/\/edcd.coriolis.io",
|
||||
"start_url": "https:\/\/coriolis.io",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
|
||||
<html>
|
||||
<head>
|
||||
<title>Coriolis EDCD Edition</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
|
||||
<!-- Standard headers -->
|
||||
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">
|
||||
<meta name="description" content="A ship builder, outfitting and comparison
|
||||
tool for Elite Dangerous">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,
|
||||
maximum-scale=1.0, user-scalable=0">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="shortcut icon" href=/favicon2.ico>
|
||||
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
|
||||
<link rel="icon" sizes="152x152 192x192" type="image/png"
|
||||
href="/192x192.png">
|
||||
|
||||
<!-- Apple/iOS headers -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
@@ -22,47 +27,35 @@
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
<meta name="theme-color" content="#000000">
|
||||
|
||||
<!-- <script data-ad-client="ca-pub-3709458261881414" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> -->
|
||||
<script>
|
||||
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
|
||||
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
|
||||
window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>';
|
||||
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
|
||||
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
|
||||
</script>
|
||||
<% if (htmlWebpackPlugin.options.uaTracking) { %>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55840909-18"></script>
|
||||
<script>
|
||||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
|
||||
ga('send', 'pageview');
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'UA-55840909-18');
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<% } %>
|
||||
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="//stats.isadankme.me/";
|
||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||
_paq.push(['setSiteId', '4']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Piwik Code -->
|
||||
|
||||
|
||||
|
||||
<!-- Bugsnag -->
|
||||
<script
|
||||
src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-3.min.js"
|
||||
data-apikey="ba9fae819372850fb660755341fa6ef5"></script>
|
||||
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
|
||||
<script>
|
||||
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
|
||||
window.Bugsnag = window.bugsnagClient
|
||||
</script>
|
||||
</head>
|
||||
<body style="background-color:#000;">
|
||||
<section id="coriolis">
|
||||
|
||||
</head>
|
||||
<body style="background-color:#000;">
|
||||
<section id="coriolis"></section>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.lib.entry %>" charset="utf-8" crossorigin="anonymous"></script>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>" charset="utf-8" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -171,3 +171,16 @@ footer {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 10px;
|
||||
justify-content: center;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.announcement {
|
||||
border: 1px @secondary solid;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user