mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Compare commits
483 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b6df8ea4f | ||
|
|
9c6f5fb44e | ||
|
|
c0a732a76b | ||
|
|
9551b8b5de | ||
|
|
22f83b79af | ||
|
|
0a26f76fb4 | ||
|
|
077f432daf | ||
|
|
e846b4508b | ||
|
|
3be78885b8 | ||
|
|
e29a4b263d | ||
|
|
061ab77de1 | ||
|
|
c6a8e48fda | ||
|
|
f48af272c3 | ||
|
|
2b1402d099 | ||
|
|
1ad0afa0d7 | ||
|
|
93d6c1b871 | ||
|
|
243f1123ba | ||
|
|
2fa2625a8f | ||
|
|
42c43d3f2d | ||
|
|
1237833c7a | ||
|
|
171a84dd33 | ||
|
|
79dac7b38f | ||
|
|
0e86ae79c1 | ||
|
|
7d7ea18447 | ||
|
|
2a6da36aeb | ||
|
|
db1a976e20 | ||
|
|
3b058bda7f | ||
|
|
da274f1b75 | ||
|
|
d624663278 | ||
|
|
d8ac185b4d | ||
|
|
b66baef998 | ||
|
|
8a115f8323 | ||
|
|
442da6f05b | ||
|
|
f536c037b1 | ||
|
|
33a678a200 | ||
|
|
b0dc4d7864 | ||
|
|
8b0e822ea9 | ||
|
|
006811f5a9 | ||
|
|
873935c1d4 | ||
|
|
94d876e934 | ||
|
|
b055963fe0 | ||
|
|
8ad9472d56 | ||
|
|
d15e49f315 | ||
|
|
c7ea1eb95a | ||
|
|
82ce86a374 | ||
|
|
a1a17bc836 | ||
|
|
75a4e54453 | ||
|
|
32fb66139a | ||
|
|
3f18987007 | ||
|
|
eb042b2778 | ||
|
|
85f108556a | ||
|
|
73a75c69a3 | ||
|
|
2f5d123f02 | ||
|
|
e278babee8 | ||
|
|
86df6f20f6 | ||
|
|
117028875f | ||
|
|
cadd699bdf | ||
|
|
6afd80002c | ||
|
|
2736e1df79 | ||
|
|
0ff95ed1f1 | ||
|
|
fcb8980a38 | ||
|
|
49a076fd9e | ||
|
|
eb83969015 | ||
|
|
369d882354 | ||
|
|
23c4da55de | ||
|
|
7259a666eb | ||
|
|
91cab5a4f1 | ||
|
|
d60a8f2625 | ||
|
|
69489aa267 | ||
|
|
750d23b10a | ||
|
|
cd753af48e | ||
|
|
365810a610 | ||
|
|
73d609610a | ||
|
|
a3c03266bf | ||
|
|
7b3ad555a1 | ||
|
|
1605e80884 | ||
|
|
0729fc29fa | ||
|
|
964cdd2b9a | ||
|
|
49c3e395db | ||
|
|
bdd2299335 | ||
|
|
f782adb21d | ||
|
|
950c0c61f9 | ||
|
|
f6ebaf7445 | ||
|
|
3b35d5030e | ||
|
|
067b69f449 | ||
|
|
6f67267fec | ||
|
|
ec0cd37896 | ||
|
|
84719c997f | ||
|
|
2731ec3b90 | ||
|
|
340121c6bd | ||
|
|
ec4e70326a | ||
|
|
3a271e4b7b | ||
|
|
02db800c7b | ||
|
|
3a55c8cc0a | ||
|
|
e00b07c2f6 | ||
|
|
fbfd0deb6c | ||
|
|
dc11cc182f | ||
|
|
597344353a | ||
|
|
9aef1ff8a6 | ||
|
|
70f218f833 | ||
|
|
5937843be8 | ||
|
|
d4b384eded | ||
|
|
2b5a42d4e2 | ||
|
|
afd8ad7678 | ||
|
|
cdc5c29458 | ||
|
|
5a351d4c0d | ||
|
|
fac279d9dd | ||
|
|
f4e5254832 | ||
|
|
2fb3ee8cd8 | ||
|
|
cc2f3fd1fe | ||
|
|
87146b2cf3 | ||
|
|
bec3ae3f89 | ||
|
|
af2e0cbed3 | ||
|
|
4bf30c0cd5 | ||
|
|
a9fdf73d86 | ||
|
|
fe691d12c7 | ||
|
|
a5df542aa2 | ||
|
|
069959dabb | ||
|
|
a0e8f19683 | ||
|
|
342ca7af05 | ||
|
|
d00c0c3904 | ||
|
|
8e0f1ca977 | ||
|
|
33360fd6cf | ||
|
|
78ad34b082 | ||
|
|
ba98fe49a9 | ||
|
|
11f5c04efa | ||
|
|
ee9f65052a | ||
|
|
a25dde8d2d | ||
|
|
8d813688f7 | ||
|
|
99fc55ba6c | ||
|
|
402a4c1939 | ||
|
|
d1e16470b8 | ||
|
|
76027b8537 | ||
|
|
fd5ff3b6a8 | ||
|
|
d2d8f084d2 | ||
|
|
ea3d57399c | ||
|
|
77d3053ff8 | ||
|
|
aea3e43e1c | ||
|
|
a949bd6738 | ||
|
|
4981ffb908 | ||
|
|
4859138053 | ||
|
|
1f3c66d9ba | ||
|
|
f2af463d00 | ||
|
|
3858712613 | ||
|
|
d7941e0a8a | ||
|
|
178d38f28d | ||
|
|
e42a0d1210 | ||
|
|
ddb89c47e7 | ||
|
|
8b9aae342b | ||
|
|
ab1d73a6ea | ||
|
|
d73a3cc2b4 | ||
|
|
230351b959 | ||
|
|
4ee5c03cd1 | ||
|
|
d8e9733170 | ||
|
|
aba2abe507 | ||
|
|
45e6b71ec9 | ||
|
|
15a14dc280 | ||
|
|
cbac650b9e | ||
|
|
f011f1f4d5 | ||
|
|
d467ad5f7c | ||
|
|
b56ac177d9 | ||
|
|
5b13d64a1d | ||
|
|
2620935745 | ||
|
|
45852db507 | ||
|
|
7fbcbb75ed | ||
|
|
abf65ee436 | ||
|
|
24849cee08 | ||
|
|
9e5efe50dc | ||
|
|
39e9a38068 | ||
|
|
73d5915b3a | ||
|
|
b06d2d1f72 | ||
|
|
6e71f5e6db | ||
|
|
588cfc3990 | ||
|
|
191e31ff18 | ||
|
|
c655b65779 | ||
|
|
3e168a3e5f | ||
|
|
ee52a4b716 | ||
|
|
a14974fef4 | ||
|
|
2c0959d68b | ||
|
|
0221d05b04 | ||
|
|
975432b45a | ||
|
|
e78551f5d9 | ||
|
|
b1daf6f799 | ||
|
|
bc31be5884 | ||
|
|
df03102c35 | ||
|
|
55e4c51d77 | ||
|
|
e7d1a0df63 | ||
|
|
ca0c9675b3 | ||
|
|
491899fe49 | ||
|
|
cc22a89ad6 | ||
|
|
d389114189 | ||
|
|
9d0f7a166b | ||
|
|
c344d56d48 | ||
|
|
eb5d0e868c | ||
|
|
c28f22a78b | ||
|
|
e2b22dd4ca | ||
|
|
f5ecccec48 | ||
|
|
7c8eee4707 | ||
|
|
99e251fd4b | ||
|
|
c1ddecfd3e | ||
|
|
0ae59c5e48 | ||
|
|
eb76040b08 | ||
|
|
7698cb75e9 | ||
|
|
bd7139c703 | ||
|
|
c9b9404ba1 | ||
|
|
84ff013a9e | ||
|
|
d2bcf6cd71 | ||
|
|
e52c6730f4 | ||
|
|
e448b3cb0a | ||
|
|
705f3158aa | ||
|
|
f4789717fe | ||
|
|
93e69f215b | ||
|
|
d04e662de4 | ||
|
|
a9f9285278 | ||
|
|
c5e27d43aa | ||
|
|
db376829f5 | ||
|
|
79a31fd992 | ||
|
|
094f3aa3e2 | ||
|
|
41d51743d3 | ||
|
|
cf741d18ed | ||
|
|
c02f674530 | ||
|
|
89f2273b5d | ||
|
|
351b084ce9 | ||
|
|
dd48d2d007 | ||
|
|
63e850b5aa | ||
|
|
46775879f7 | ||
|
|
c9ba4e3fcb | ||
|
|
8b855b62a1 | ||
|
|
d6a687300c | ||
|
|
ddc968129d | ||
|
|
e364560ca7 | ||
|
|
baf6f8130a | ||
|
|
f8f5cd2581 | ||
|
|
8019453d0d | ||
|
|
6ad17595dc | ||
|
|
50e1f7d4df | ||
|
|
fdb931fe00 | ||
|
|
86efb59237 | ||
|
|
f05aa66e53 | ||
|
|
f4e95cd150 | ||
|
|
5798c0e8a6 | ||
|
|
22f98fa8a7 | ||
|
|
897668c0d4 | ||
|
|
99c0bfcee1 | ||
|
|
37bfc700e9 | ||
|
|
8fcebf59f6 | ||
|
|
486c055631 | ||
|
|
92246302b9 | ||
|
|
0d646c6193 | ||
|
|
217ea3c5ac | ||
|
|
fcf0494df6 | ||
|
|
20ba6eb822 | ||
|
|
0e2c0349e0 | ||
|
|
c49e2cff03 | ||
|
|
d79313bfbe | ||
|
|
fd404b5155 | ||
|
|
49e72146b4 | ||
|
|
9b534b62c8 | ||
|
|
0be59af9b0 | ||
|
|
5d87a6cd56 | ||
|
|
c61c17d465 | ||
|
|
029ba63aa5 | ||
|
|
99e9e0c76f | ||
|
|
5bbc6e1cbe | ||
|
|
1e5f66e528 | ||
|
|
cdb837a25a | ||
|
|
dd1175abf4 | ||
|
|
619976230d | ||
|
|
2cb0d5209b | ||
|
|
ad06e23afa | ||
|
|
3def84e435 | ||
|
|
7f377d6345 | ||
|
|
53137e0ae1 | ||
|
|
792eda2572 | ||
|
|
550c94fa94 | ||
|
|
2a841281d4 | ||
|
|
260f29834a | ||
|
|
ddb35d321c | ||
|
|
6017c1ecff | ||
|
|
05e06f30f5 | ||
|
|
fb5ba6a0b2 | ||
|
|
80656a7a78 | ||
|
|
ce980cf091 | ||
|
|
a4656e223a | ||
|
|
66d4b5ac4c | ||
|
|
6961469ae5 | ||
|
|
06f4abdf8b | ||
|
|
7855d0e171 | ||
|
|
40f213c883 | ||
|
|
be1bfeb6f3 | ||
|
|
b40a2e96e0 | ||
|
|
5f036c586c | ||
|
|
091789c819 | ||
|
|
0ce8bfac79 | ||
|
|
e53ffd0273 | ||
|
|
bb7db144d6 | ||
|
|
2e42a328e0 | ||
|
|
f82122f29f | ||
|
|
5bf907809d | ||
|
|
51d7b6c9aa | ||
|
|
b8cff0c2fc | ||
|
|
6ac69a6388 | ||
|
|
32282141cf | ||
|
|
059c2badf4 | ||
|
|
fb090618da | ||
|
|
9ed0e30538 | ||
|
|
af82b8ca1e | ||
|
|
6e18793d82 | ||
|
|
22e74164c5 | ||
|
|
93ba1bf67a | ||
|
|
46ed9003dd | ||
|
|
5603315bf0 | ||
|
|
5bbc6be3d8 | ||
|
|
203e9c7b46 | ||
|
|
2a6850ded0 | ||
|
|
041f873f97 | ||
|
|
b944035541 | ||
|
|
7c6a4fc5f8 | ||
|
|
5426b55637 | ||
|
|
a6a10df39c | ||
|
|
0dc58bad7e | ||
|
|
794faacbd4 | ||
|
|
0a37b36ec2 | ||
|
|
85e6796e88 | ||
|
|
fa1ef47b71 | ||
|
|
f31e3c09f4 | ||
|
|
e6ab536601 | ||
|
|
5bced9fe56 | ||
|
|
67742060d3 | ||
|
|
ca2136544c | ||
|
|
ee19e9af50 | ||
|
|
f457fd0bff | ||
|
|
c1ce07e039 | ||
|
|
c8d1536f77 | ||
|
|
231ad4af59 | ||
|
|
d5f61d7ae8 | ||
|
|
8a5d4a36bf | ||
|
|
2c9237626d | ||
|
|
37f889e317 | ||
|
|
f86ba48295 | ||
|
|
aac35633a3 | ||
|
|
e73e0a305d | ||
|
|
4b2b0efe37 | ||
|
|
8fe20f6f65 | ||
|
|
11af7f567a | ||
|
|
3b8444482f | ||
|
|
c09e1b1b3e | ||
|
|
5770cf8d39 | ||
|
|
6da09f2e5d | ||
|
|
294fadf7cd | ||
|
|
2a97678574 | ||
|
|
76b3bd34f5 | ||
|
|
02bfecb92d | ||
|
|
719759ad56 | ||
|
|
fd446b29ba | ||
|
|
e5552d3e10 | ||
|
|
50946eeeb8 | ||
|
|
faab41117c | ||
|
|
0ab59c1f9a | ||
|
|
1067dceaa3 | ||
|
|
9042de422a | ||
|
|
f0547feb93 | ||
|
|
f863daa347 | ||
|
|
fdb202e7d6 | ||
|
|
c6bde19052 | ||
|
|
f6aff3d3bb | ||
|
|
2f4a2ebe03 | ||
|
|
ca20e94b93 | ||
|
|
40a87dceeb | ||
|
|
95b7d60be4 | ||
|
|
24abd6583f | ||
|
|
8857aba53f | ||
|
|
e4830811b0 | ||
|
|
143380ac58 | ||
|
|
a2f6fb6ac0 | ||
|
|
0d3c128059 | ||
|
|
930a555425 | ||
|
|
d6f213fbe7 | ||
|
|
0571e8e099 | ||
|
|
54c61ecb7d | ||
|
|
030867c4f8 | ||
|
|
33a7c71fec | ||
|
|
4e0f682ad6 | ||
|
|
4486aa2e2b | ||
|
|
0c94c81746 | ||
|
|
5b037e3a00 | ||
|
|
42a2b907ce | ||
|
|
a65dae1631 | ||
|
|
7d4c534956 | ||
|
|
8397d3505b | ||
|
|
9556f28ba4 | ||
|
|
f489257f86 | ||
|
|
c96693c439 | ||
|
|
3d4f6d7861 | ||
|
|
606eabfec7 | ||
|
|
782603727a | ||
|
|
ad570534a0 | ||
|
|
87e903e473 | ||
|
|
cf6d32ea04 | ||
|
|
5b81a0b25f | ||
|
|
0688faac93 | ||
|
|
3719bb9696 | ||
|
|
21582d1598 | ||
|
|
9a9607fcfb | ||
|
|
8602c5667b | ||
|
|
f13a987388 | ||
|
|
38eaebefc0 | ||
|
|
c1bc514e6b | ||
|
|
42e98fd015 | ||
|
|
5f0b851de7 | ||
|
|
616ed0bf10 | ||
|
|
108ab3b1ee | ||
|
|
04caef9613 | ||
|
|
cae7edbe01 | ||
|
|
2fa96fc1a5 | ||
|
|
6b8a4d1f85 | ||
|
|
07df44a907 | ||
|
|
be45637435 | ||
|
|
87ab684ba3 | ||
|
|
916a6d5e48 | ||
|
|
b99fbf07f5 | ||
|
|
6d6ef2a93e | ||
|
|
d08e5e2858 | ||
|
|
c1f4a8d416 | ||
|
|
c17c7125e3 | ||
|
|
97fc4ce45d | ||
|
|
abfc338240 | ||
|
|
3d129946ce | ||
|
|
2aab31d317 | ||
|
|
f8ff9a6a87 | ||
|
|
6727c2fc13 | ||
|
|
ba1f11a88b | ||
|
|
c88e4369c8 | ||
|
|
c490f97c22 | ||
|
|
7c71555384 | ||
|
|
35538f971a | ||
|
|
b3c82ac2de | ||
|
|
ff0d8dccea | ||
|
|
06841b1485 | ||
|
|
f55448c0a8 | ||
|
|
258701c377 | ||
|
|
1a0f05511b | ||
|
|
3ec9679893 | ||
|
|
e5cc3e269e | ||
|
|
4b14f617ec | ||
|
|
0df051e40f | ||
|
|
2b464f34e5 | ||
|
|
7a33bd64f8 | ||
|
|
3656c7a18f | ||
|
|
3114852c63 | ||
|
|
45337913ba | ||
|
|
adc5d1c039 | ||
|
|
183f22c223 | ||
|
|
43eb4935e6 | ||
|
|
2c45011664 | ||
|
|
f29f3f4f8f | ||
|
|
c347336055 | ||
|
|
cd87184169 | ||
|
|
09895852c3 | ||
|
|
32759d3e0e | ||
|
|
965134fc02 | ||
|
|
7da5360349 | ||
|
|
bf0701340c | ||
|
|
a3a490f442 | ||
|
|
33b22b8280 | ||
|
|
e6278858b9 | ||
|
|
ed7a9ef037 | ||
|
|
9c42179617 | ||
|
|
5634fbd568 | ||
|
|
7d99d59790 | ||
|
|
5f75c9d32b | ||
|
|
fe1d16415e | ||
|
|
ed8cbb403e | ||
|
|
58182bddce | ||
|
|
771ba7e692 | ||
|
|
ed0d4ddc48 | ||
|
|
266c63bc0c | ||
|
|
ebcc92f7d4 | ||
|
|
a1f280cf62 | ||
|
|
0306167e6e | ||
|
|
162136194a | ||
|
|
3600a4862f | ||
|
|
65fdad64b2 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,3 +3,7 @@ build
|
||||
.DS_Store
|
||||
*.log
|
||||
nginx.pid
|
||||
.idea
|
||||
/bin
|
||||
env
|
||||
*.swp
|
||||
|
||||
279
ChangeLog.md
Normal file
279
ChangeLog.md
Normal file
@@ -0,0 +1,279 @@
|
||||
#2.3.3
|
||||
* Remove unused blueprint when hitting reset
|
||||
* Add 'purchase module' external link to EDDB for refit items
|
||||
* Use coriolis-data 2.3.3:
|
||||
* Add Felicity Farseer to list of engineers that supply sensor and detailed surface scanner modifications
|
||||
|
||||
#2.3.2
|
||||
* Use scan range for DSS rather than scan time
|
||||
* Fix companion API import of Dolphin
|
||||
* Use coriolis-data 2.3.2:
|
||||
* Separate scan time and scan range
|
||||
* Add Frontier IDs for new items in 2.3
|
||||
* Update ownership of module blueprints for sensors and scanners
|
||||
* Update railgun penetration
|
||||
|
||||
#2.3.0
|
||||
* Make scan time visible on scanners where available
|
||||
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
|
||||
* Revert to floating header due to issues on iOS
|
||||
* Fix issue where new module added to a slot did not reset its enabled status
|
||||
* Show integrity value for relevant modules
|
||||
* Reset old modification values when a new roll is applied
|
||||
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
|
||||
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
|
||||
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
|
||||
* Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
|
||||
* Use opponent's saved pips if available
|
||||
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
|
||||
* Ensure that clip size modification imports result in whole numbers
|
||||
* Rework of separate offence/defence/movement sections to a unified interface
|
||||
* Use cargo hatch information on import if available
|
||||
* Additional information of power distributor pips, boost, cargo and fuel loads added to build
|
||||
* Additional information of opponent and engagement range added to build
|
||||
* Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
|
||||
* Power and costs section provides the existing 'Power' and 'Costs' sections
|
||||
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
|
||||
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
|
||||
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
|
||||
* Fix power band marker to show safe power limit at 40% rather than 50%
|
||||
* Restyle blueprint list to improve consistency with similar menus
|
||||
* Use coriolis-data 2.3.0:
|
||||
* Add Dolphin
|
||||
* Add turreted mining lasers
|
||||
* Add long range / wide angle / fast scan scanner blueprints
|
||||
* Fix EDDB IDs for class 5 and 7 fighter hangars for correct shopping list
|
||||
* Fix cost for rocket-propelled FSD disruptor
|
||||
* Add module names for blueprints
|
||||
* Fix erroneous value for grade 5 kinetic shield booster
|
||||
* Add missing integrity values for some modules
|
||||
* Update module reinforcement package integrity
|
||||
* Update specs of Beluga as per 2.3
|
||||
* Update specs of Asp Scout as per 2.3
|
||||
* Update specs of Diamondback Explorer as per 2.3
|
||||
* Add ED ID for Rocket Propelled FSD Disruptor
|
||||
* Fix ED name for target lock breaker special
|
||||
* Update scan range and angle information for sensors
|
||||
* Tidy up shield cell bank information to allow for accurate calculations with modifications
|
||||
* Update mine launcher stats
|
||||
* Add appropriate engineers to per-module blueprint information
|
||||
|
||||
#2.2.19
|
||||
* Power management panel now displays modules in descending order of power usage by default
|
||||
* Shot speed can no longer be modified directly. Its value is derived from the range modifier for Long Range and Focused modifications
|
||||
* Ensure that jump range chart updates when fuel slider is changed
|
||||
* Add 'Engine profile' and 'FSD profile' charts. These show how your maximum speed/jump range will alter as you alter the mass of your build
|
||||
* Use coriolis-data 2.2.19:
|
||||
* Remove shot speed modification - it is directly tied to range
|
||||
* Fix incorrect minimal mass for 3C bi-weave shield generator
|
||||
|
||||
#2.2.18
|
||||
* Change methodology for calculating explorer role; can result in lighter builds
|
||||
* Tidy up layout for module selection and lay everything out in a consistent best-to-worst for both class and grade
|
||||
* Make integrity for module reinforcement packages visible
|
||||
* Clean up breakpoints for modules in available modules list; stops 7- or 8- module long lines
|
||||
* Add damager/range graphs to damage dealt
|
||||
* Reorder panels
|
||||
* Use coriolis-data 2.2.18:
|
||||
* Correct lower efficiency value to be better, not worse
|
||||
|
||||
#2.2.17
|
||||
* Use in-game terminology for shield generator optmul and optmass items
|
||||
* Add crew to shipyard and outfitting page information
|
||||
* Use coriolis-data 2.2.17:
|
||||
* Add mass as potential SCB modification
|
||||
* Fix mining laser statistics
|
||||
* Remove non-existent grade 4 and 5 wake scanner modifications
|
||||
* Add number of crew for each ship
|
||||
|
||||
#2.2.16
|
||||
* Fix 'Extreme' blueprint roll where some incorrect ranges were chosen
|
||||
* Use coriolis-data 2.2.16:
|
||||
* Fix incorrect thermal load modifiers for dirty drives
|
||||
* Provide explicit information about if values are higher numeric value == better or not
|
||||
|
||||
#2.2.15
|
||||
* Ensure that standard slots are repainted when any component changes
|
||||
* Reload page if Safari throws a security error
|
||||
* Handle import of ships with incorrectly-sized slots
|
||||
* Add 'Extreme' blueprint roll: best beneficial and worst detrimental outcome (in place of 'Average' roll)
|
||||
* Display information about Microsoft browser issues when an import fails
|
||||
* Add 'purchase this build' icon link to EDDB
|
||||
* Add 'miner' and 'shielded miner' ship roles
|
||||
* Use coriolis-data 2.2.15:
|
||||
* Fix location of initial cargo rack for Vulture
|
||||
* Fix broken regeneration rate for 6B shield generators
|
||||
* Tidy up breach damage values
|
||||
|
||||
#2.2.14
|
||||
* Ensure that jitter is shown correctly when the result of a special effect
|
||||
* Use restyled blueprint information
|
||||
* Use the ship name (if available) rather than the ship model for the window title
|
||||
* Use coriolis-data 2.2.14:
|
||||
* Alter blueprint structure to combine components and features
|
||||
* Make hidden value of modifications its own attribute
|
||||
* Fix incorrect ED ID for class 6 passenger cabins
|
||||
|
||||
#2.2.13
|
||||
* Add 'time to drain' summary value. This is the time to drain the WEP capacitor if firing all enabled weapons
|
||||
* Do not include utility slot DPS/EPS/HPS in summary information
|
||||
* Ensure that auto loader special shows in the tooltip
|
||||
* Ensure that ship mass is recalculated when appropriate
|
||||
* Use coriolis-data 2.2.13:
|
||||
* Add plasma slug special effect for plasma accelerator
|
||||
* Tweak hull costs of ships
|
||||
|
||||
#2.2.12
|
||||
* Tidy up old references to coriolis.io
|
||||
* Add ability to add and remove special effects to weapon modifications
|
||||
* Add weapon engineering information to Damage Dealt section
|
||||
* Change shortcut for link from ctrl-l to ctrl-o to avoid clash with location bar
|
||||
* Only show one of power generation or draw in tooltips, according to module
|
||||
* Use coriolis-data 2.2.12:
|
||||
* Add special effects for each blueprint
|
||||
* Add IDs for most Powerplay modules
|
||||
|
||||
#2.2.11
|
||||
* Add help system and initial help file
|
||||
* Make absolute damage visible
|
||||
* Add 'average' roll for blueprints
|
||||
* Update spacing for movement summary to make it more readable
|
||||
* Provide damage dealt statistics for both shields and hull
|
||||
* Damage dealt panel only shows enabled weapons
|
||||
* Add engagement range to damage received panel
|
||||
* Handle burst rate of fire as an absolute number rather than a perentage modification
|
||||
* Ensure that clip values are always rounded up
|
||||
* Ensure that focused weapon mod uses range modifier to increase falloff as well
|
||||
* Use coriolis-data 2.2.11:
|
||||
* Remove non-existent chaff launcher capacity blueprint grades
|
||||
* Fix incorrect values for charge enhanced power distributor
|
||||
* Remove incorrect AFMU blueprints
|
||||
* Correct fragment cannon Double Shot blueprint information
|
||||
* Correct Focused weapon blueprint information
|
||||
|
||||
#2.2.10
|
||||
* Fix detailed export of module reinforcement packages
|
||||
* Use damagedist for exact breakdown of weapons that have more than one type of damage
|
||||
* Use new-style modification validity data
|
||||
* Provide ability to select engineering blueprint and roll sample values for them
|
||||
* Use coriolis-data 2.2.10:
|
||||
* Fix incorrect base shield values for Cutter and Corvette
|
||||
* Update weapons to have %-based damage distributions
|
||||
* Remove power draw for detailed surface scanner - although shown in outfitting it is not part of active power
|
||||
* Fix incorrect names for lightweight and kinetic armour
|
||||
* Add engineering blueprints
|
||||
|
||||
#2.2.9
|
||||
* Use SSL-enabled server for shortlinks
|
||||
* Add falloff for weapons
|
||||
* Use falloff when calculating weapon effectiveness in damage dealt
|
||||
* Add engagement range slider to 'Damage Dealt' section to allow user to see change in weapon effectiveness with range
|
||||
* Use better DPE calculation methodology
|
||||
* Add total DPS and effectiveness information to 'Damage Dealt' section
|
||||
* Use coriolis-data 2.2.9:
|
||||
* Add falloff metric for weapons
|
||||
* Add falloff from range modification
|
||||
|
||||
#2.2.8
|
||||
* Fix issue where filling all internals with cargo racks would include restricted slots
|
||||
* Use coriolis-data 2.2.8:
|
||||
* Set military slot of Viper Mk IV to class 3; was incorrectly set as class 2
|
||||
* Update base regeneration rate of prismatic shield generators to values in 2.2.03
|
||||
* Update specials with information in 2.2.03
|
||||
|
||||
#2.2.7
|
||||
* Fix resistance diminishing return calculations
|
||||
* Do not allow -100% to be entered as a modification value
|
||||
|
||||
#2.2.6
|
||||
* Add pitch/roll/yaw information
|
||||
* Use combination of pitch, roll and yaw to provide a more useful agility metric
|
||||
* Add movement summary to outfitting page
|
||||
* Add standard internal class sizes to shipyard page
|
||||
* Fix issue when importing Viper Mk IV
|
||||
* Ensure ordering of all types of modules (standard, internal, utilities) is consistent
|
||||
* Add rebuilds per bay information for fighter hangars
|
||||
* Add ability to show military compartments
|
||||
* Show module reinforcement package results in defence summary
|
||||
* Use separate speed/rotation/acceleration multipliers for thrusters if available
|
||||
* Obey restricted slot rules when adding all for internal slots
|
||||
* Version URLs to handle changes to ship specifications over time
|
||||
* Do not include disabled shield boosters in calculations
|
||||
* Add 'Damage dealt' section
|
||||
* Add 'Damage received' section
|
||||
* Add 'Piercing' information to hardpoints
|
||||
* Add 'Hardness' information to ship summary
|
||||
* Add module copy functionality - drag module whilst holding 'alt' to copy
|
||||
* Add base resistances to defence summary tooltip
|
||||
* Update shield recovery/regeneration calculations
|
||||
* Pin menu to top of page
|
||||
* Switch to custom shortlink method to avoid google length limitations
|
||||
* Ensure that information is not lost on narrow screens
|
||||
* Do not lose ship selector selection on narrow screens
|
||||
* Reinstate jump range graph
|
||||
* Use coriolis-data 2.2.6:
|
||||
* Update weapons with changed values for 2.2.03
|
||||
* Add individual pitch/roll/yaw statistics for each ship
|
||||
* Remove old and meaningless agility stat
|
||||
* Use sane order for multi-module JSON - coriolis can re-order as it sees fit when displaying modules
|
||||
* Fix cost of fighter hangars
|
||||
* Update Powerplay weapons with current statistics
|
||||
* Add separate min/opt/max multipliers for enhanced thrusters for speed, acceleration and rotation
|
||||
* Add module reinforcement packages
|
||||
* Add military compartments
|
||||
* Fix missing damage value for 2B dumbfires
|
||||
* Update shield recharge rates
|
||||
* Reduce hull mass of Viper to 50T
|
||||
* Fix incorrect optimal mass value for 8A thrusters
|
||||
* Add power draw for detailed surface scanner
|
||||
|
||||
#2.2.5
|
||||
* Calculate rate of fire for multi-burst weapons
|
||||
* Add note to disable ghostery in error situation
|
||||
* Use coriolis-data 2.2.5:
|
||||
* Fix incorrect ID for emissive munitions special
|
||||
* Fix rate of fire for burst lasers
|
||||
* Add fragment cannon modifications
|
||||
* Fix internal name of dazzle shell
|
||||
|
||||
#2.2.4
|
||||
* Add shortlink for outfitting page
|
||||
* Use coriolis-data 2.2.4:
|
||||
* Fix incorrect ID for class 5 luxury passenger cabin
|
||||
* Add damage type modifier
|
||||
* Change modifications from simple strings to objects, to allow more data-driven behaviour
|
||||
* Add special effects
|
||||
* Modification tooltip now shows special effect
|
||||
|
||||
#2.2.3
|
||||
* Fix hull boost calculation - now shows correct % modifier and total armour
|
||||
* Fix import of DiamondBack - can now be imported
|
||||
* Fix import of Beluga - can now be imported
|
||||
* Use coriolis-data 2.2.3:
|
||||
* Fix mismatch between class 5 and class 7 fighter hangars - now shows correct module
|
||||
* Add details for concordant sequence special effect - now shows correct damage
|
||||
* Fix details for thermal shock special effect - now shows correct damage
|
||||
* Add engineer blueprints
|
||||
* Modification tooltip now shows name and grade of modifications for imported builds
|
||||
* Retain import URL unless user changes the build - allows future updates of Coriolis to take advantage of additional build information
|
||||
|
||||
#2.2.2
|
||||
* Update DPS/HPS/EPS in real-time as modifiers change
|
||||
* Use coriolis-data 2.2.2:
|
||||
* Add distributor draw modifier to shield generators
|
||||
* Remove modifiers for sensors
|
||||
* Add initial loadout passenger cabins for Beluga
|
||||
* Add initial loadout passenger cabins for Orca
|
||||
* Update costs and initial loadouts for Keelback and Type-7
|
||||
* Add resistances for hull reinforcement packages
|
||||
* Added modifier actions to create modifications from raw data
|
||||
* Show modification icon for modified modules
|
||||
* Take modifications in to account when deciding whether to issue a warning on a standard module
|
||||
* Fix hardpoint comparison DPS number when selecting an alternate module
|
||||
* Ensure that retrofit tab only shows changed modules
|
||||
* Fix import and export of ships with modifications, bump schema version to 4
|
||||
* Enable boost display even if power distributor is disabled
|
||||
* Calculate breakdown of ship offensive and defensive stats
|
||||
* Add 'Offence summary' and 'Defence summary' components
|
||||
* Add ability to import from companion API output through import feature
|
||||
* Add ability to import from companion API output through URL
|
||||
13
README.md
13
README.md
@@ -1,5 +1,4 @@
|
||||
 [](https://travis-ci.org/cmmcleod/coriolis) [](https://waffle.io/cmmcleod/coriolis) [](http://waffle.io/cmmcleod/coriolis) [](https://www.hipchat.com/gfYQiZcmy)
|
||||
|
||||
 [](https://travis-ci.org/EDCD/coriolis) [](https://discord.gg/0uwCh6R62aPRjk9w)
|
||||
|
||||
## About
|
||||
|
||||
@@ -9,17 +8,15 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
|
||||
|
||||
## Contributing
|
||||
|
||||
Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](https://github.com/cmmcleod/coriolis/pulls) for any corrections or additions to the database or the code.
|
||||
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
|
||||
|
||||
### Feature Requests, Suggestions & Bugs
|
||||
|
||||
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)!
|
||||
|
||||
All such requests are managed and tracked through [issues](https://github.com/cmmcleod/coriolis/issues). An overview of these can be found [here](https://waffle.io/cmmcleod/coriolis).
|
||||
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
|
||||
## Development
|
||||
|
||||
See the [Developer's Guide](https://github.com/cmmcleod/coriolis/wiki/Developer's-Guide) in the wiki.
|
||||
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
|
||||
|
||||
|
||||
### Ship and Module Database
|
||||
@@ -29,7 +26,7 @@ See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details
|
||||
|
||||
## License
|
||||
|
||||
All Data and [associated JSON](https://github.com/cmmcleod/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
|
||||
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.
|
||||
|
||||
30
__tests__/fixtures/agility-data.json
Normal file
30
__tests__/fixtures/agility-data.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"adder": {
|
||||
"t3": {"speed": 205, "boost": 298, "pitch": 35.37, "roll": 93.09, "yaw": 13.03},
|
||||
"t2": {"speed": 209, "boost": 304, "pitch": 36.06, "roll": 94.90, "yaw": 13.29},
|
||||
"t1": {"speed": 213, "boost": 310, "pitch": 36.80, "roll": 96.84, "yaw": 13.56},
|
||||
"t0": {"speed": 218, "boost": 317, "pitch": 37.70, "roll": 99.20, "yaw": 13.89},
|
||||
"t9": {"speed": 220, "boost": 321, "pitch": 38.08, "roll": 100.21, "yaw": 14.03},
|
||||
"t8": {"speed": 225, "boost": 327, "pitch": 38.86, "roll": 102.26, "yaw": 14.32},
|
||||
"t7": {"speed": 230, "boost": 334, "pitch": 39.69, "roll": 104.44, "yaw": 14.62},
|
||||
"t6": {"speed": 234, "boost": 340, "pitch": 40.41, "roll": 106.34, "yaw": 14.89},
|
||||
"t5": {"speed": 242, "boost": 351, "pitch": 41.71, "roll": 109.78, "yaw": 15.37}
|
||||
},
|
||||
"eagle": {
|
||||
"t2": {"speed": 223, "boost": 325, "pitch": 46.45, "roll": 111.48, "yaw": 16.72},
|
||||
"t1": {"speed": 229, "boost": 334, "pitch": 47.69, "roll": 114.46, "yaw": 17.17},
|
||||
"t0": {"speed": 235, "boost": 343, "pitch": 49.00, "roll": 117.60, "yaw": 17.64},
|
||||
"t9": {"speed": 239, "boost": 349, "pitch": 49.80, "roll": 119.53, "yaw": 17.93},
|
||||
"t8": {"speed": 243, "boost": 355, "pitch": 50.70, "roll": 121.69, "yaw": 18.25},
|
||||
"t7": {"speed": 248, "boost": 361, "pitch": 51.62, "roll": 123.89, "yaw": 18.58},
|
||||
"t6": {"speed": 252, "boost": 367, "pitch": 52.46, "roll": 125.91, "yaw": 18.89},
|
||||
"t5": {"speed": 259, "boost": 378, "pitch": 53.99, "roll": 129.56, "yaw": 19.43}
|
||||
},
|
||||
"hauler": {
|
||||
"t4": {"speed": 203, "boost": 305, "pitch": 36.61, "roll": 101.71, "yaw": 14.24},
|
||||
"t3": {"speed": 209, "boost": 314, "pitch": 37.63, "roll": 104.54, "yaw": 14.64},
|
||||
"t2": {"speed": 216, "boost": 324, "pitch": 38.89, "roll": 108.03, "yaw": 15.12},
|
||||
"t1": {"speed": 222, "boost": 333, "pitch": 39.97, "roll": 111.02, "yaw": 15.54},
|
||||
"t0": {"speed": 232, "boost": 348, "pitch": 41.76, "roll": 116.00, "yaw": 16.24}
|
||||
}
|
||||
}
|
||||
325
__tests__/fixtures/anaconda-test-detailed-export-v4.json
Normal file
325
__tests__/fixtures/anaconda-test-detailed-export-v4.json
Normal file
@@ -0,0 +1,325 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Test My Ship",
|
||||
"ship": "Anaconda",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship",
|
||||
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
|
||||
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
|
||||
"shipId": "anaconda"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Reactive Surface Composite",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"pgen": 1000
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 5,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Plasma Accelerator",
|
||||
"mount": "Fixed"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
}
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Kill Warrant Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "F",
|
||||
"enabled": false,
|
||||
"priority": 1,
|
||||
"group": "Electronic Countermeasure",
|
||||
"name": "Electronic Countermeasure"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Chaff Launcher",
|
||||
"name": "Chaff Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Point Defence",
|
||||
"name": "Point Defence"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 7,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Cell Bank"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Hull Reinforcement Package"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Frame Shift Drive Interdictor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 3,
|
||||
"fighterHangars": 1,
|
||||
"hullCost": 141889930,
|
||||
"speed": 180,
|
||||
"topSpeed": 186.5,
|
||||
"boost": 240,
|
||||
"boostEnergy": 27,
|
||||
"topBoost": 249.34,
|
||||
"topPitch": 25.97,
|
||||
"topRoll": 62.34,
|
||||
"topYaw": 10.39,
|
||||
"topSpeed": 187.01,
|
||||
"totalCost": 882362058,
|
||||
"totalDpe": 142.68,
|
||||
"totalDps": 101.13,
|
||||
"totalEps": 18.71,
|
||||
"totalExplDpe": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalAbsDpe": 3.57,
|
||||
"totalAbsDps": 18.78,
|
||||
"totalAbsSDps": 14.45,
|
||||
"totalHps": 28.28,
|
||||
"totalKinDpe": 117.48,
|
||||
"totalKinDps": 22.27,
|
||||
"totalKinSDps": 16.91,
|
||||
"totalSDps": 89.99,
|
||||
"totalThermDpe": 21.63,
|
||||
"totalThermDps": 60.08,
|
||||
"totalThermSDps": 58.64,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullExplRes": 0.22,
|
||||
"hullKinRes": 0.27,
|
||||
"hullMass": 400,
|
||||
"hullThermRes": -0.36,
|
||||
"masslock": 23,
|
||||
"pipSpeed": 0.14,
|
||||
"pitch": 25,
|
||||
"moduleCostMultiplier": 1,
|
||||
"modulearmour": 0,
|
||||
"moduleprotection": 0,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 128,
|
||||
"ladenMass": 1323.2,
|
||||
"armour": 2227.5,
|
||||
"baseArmour": 525,
|
||||
"unladenMass": 1163.2,
|
||||
"powerAvailable": 39.6,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.13,
|
||||
"roll": 60,
|
||||
"unladenRange": 18.74,
|
||||
"yaw": 10,
|
||||
"fullTankRange": 18.36,
|
||||
"hardness": 65,
|
||||
"ladenRange": 16.59,
|
||||
"unladenFastestRange": 74.2,
|
||||
"ladenFastestRange": 66.96,
|
||||
"maxJumpCount": 4,
|
||||
"shield": 833,
|
||||
"shieldCells": 1840,
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.4,
|
||||
"shieldThermRes": -0.2,
|
||||
"crew": 3
|
||||
}
|
||||
}
|
||||
255
__tests__/fixtures/asp-test-detailed-export-v4.json
Normal file
255
__tests__/fixtures/asp-test-detailed-export-v4.json
Normal file
@@ -0,0 +1,255 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Multi-purpose Asp Explorer",
|
||||
"ship": "Asp Explorer",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "https://coriolis.edcd.io/outfit/asp?code=0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer",
|
||||
"code": "0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx/78YG5AltB7I/8/0TwImJboDSPJ/+/f/v/KlX///i3AwMTBIfARK/Gf+JwVSxArStVAYqOjvz///JVo5GRhE2IBc4SKQSSz/DGEmCa398P8//2+gTf//AwDFxwtofAAAAA==",
|
||||
"shipId": "asp"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Lightweight Alloy",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"modifications": {
|
||||
"eff": -1850,
|
||||
"pgen": 6,
|
||||
"mass": 431
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 64,
|
||||
"name": "Low emissions",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"optmul": 440,
|
||||
"integrity": -266,
|
||||
"thermload": -1326,
|
||||
"optmass": 520,
|
||||
"power": 241
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 24,
|
||||
"name": "Clean",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": 5025,
|
||||
"integrity": -1539,
|
||||
"power": 2437,
|
||||
"optmass": 4870,
|
||||
"maxfuel": 370
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 26,
|
||||
"name": "Increased range",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": -3923,
|
||||
"integrity": -1797
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 49,
|
||||
"name": "Lightweight",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 5,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Point Defence",
|
||||
"name": "Point Defence"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "G",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Planetary Vehicle Hangar"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Advanced Discovery Scanner"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Detailed Surface Scanner"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 2,
|
||||
"hullCost": 6135660,
|
||||
"speed": 250,
|
||||
"boost": 340,
|
||||
"boostEnergy": 13,
|
||||
"agility": 6,
|
||||
"baseShieldStrength": 140,
|
||||
"baseArmour": 210,
|
||||
"hullMass": 280,
|
||||
"masslock": 11,
|
||||
"pipSpeed": 0.13,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 40,
|
||||
"ladenMass": 435.26,
|
||||
"armour": 378,
|
||||
"shield": 113.43,
|
||||
"shieldCells": 0,
|
||||
"totalCost": 48402550,
|
||||
"unladenMass": 363.26,
|
||||
"totalDpe": 0,
|
||||
"totalExplDpe": 0,
|
||||
"totalKinDpe": 0,
|
||||
"totalThermDpe": 0,
|
||||
"totalDps": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalKinDps": 0,
|
||||
"totalThermDps": 0,
|
||||
"totalSDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalKinSDps": 0,
|
||||
"totalThermSDps": 0,
|
||||
"totalEps": 1.2,
|
||||
"totalHps": 1,
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.6,
|
||||
"shieldThermRes": 1.2,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"powerAvailable": 20.41,
|
||||
"powerRetracted": 11.91,
|
||||
"powerDeployed": 11.91,
|
||||
"unladenRange": 50.45,
|
||||
"fullTankRange": 47.03,
|
||||
"ladenRange": 42.71,
|
||||
"unladenFastestRange": 317.24,
|
||||
"ladenFastestRange": 287.02,
|
||||
"maxJumpCount": 7,
|
||||
"topSpeed": 274.01,
|
||||
"topBoost": 372.65
|
||||
}
|
||||
}
|
||||
1288
__tests__/fixtures/companion-api-import-1.json
Normal file
1288
__tests__/fixtures/companion-api-import-1.json
Normal file
File diff suppressed because it is too large
Load Diff
552
__tests__/fixtures/companion-api-import-2.json
Normal file
552
__tests__/fixtures/companion-api-import-2.json
Normal file
@@ -0,0 +1,552 @@
|
||||
{
|
||||
"cargo": {
|
||||
"capacity": 32
|
||||
},
|
||||
"free": false,
|
||||
"fuel": {
|
||||
"main": {
|
||||
"capacity": 128
|
||||
},
|
||||
"reserve": {
|
||||
"capacity": 0.81
|
||||
}
|
||||
},
|
||||
"id": 31,
|
||||
"modules": {
|
||||
"Armour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049346,
|
||||
"name": "BelugaLiner_Armour_Grade1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Bobble01": [],
|
||||
"Bobble02": [],
|
||||
"Bobble03": [],
|
||||
"Bobble04": [],
|
||||
"Bobble05": [],
|
||||
"Bobble06": [],
|
||||
"Bobble07": [],
|
||||
"Bobble08": [],
|
||||
"Bobble09": [],
|
||||
"Bobble10": [],
|
||||
"Decal1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667757,
|
||||
"name": "Decal_Explorer_Ranger",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667742,
|
||||
"name": "Decal_Combat_Deadly",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667750,
|
||||
"name": "Decal_Trade_Tycoon",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"EngineColour": [],
|
||||
"FrameShiftDrive": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064132,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 175,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_mass",
|
||||
"type": 1,
|
||||
"value": 0.4457540512085
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.24584779143333
|
||||
},
|
||||
{
|
||||
"name": "mod_passive_power",
|
||||
"type": 1,
|
||||
"value": 0.24457727372646
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_optimised_mass",
|
||||
"type": 1,
|
||||
"value": 0.49257898330688
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_max_fuel_per_jump",
|
||||
"type": 2,
|
||||
"value": 0.028505677357316
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_heat_rate",
|
||||
"type": 2,
|
||||
"value": -0.079360365867615
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
16
|
||||
],
|
||||
"recipeID": 128673694,
|
||||
"slotIndex": 53
|
||||
},
|
||||
"name": "Int_Hyperdrive_Size7_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"recipeLevel": 5,
|
||||
"recipeName": "FSD_LongRange",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 46160201
|
||||
}
|
||||
},
|
||||
"FuelTank": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064352,
|
||||
"name": "Int_FuelTank_Size7_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 1602822,
|
||||
"value": 1602822
|
||||
}
|
||||
},
|
||||
"LifeSupport": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064174,
|
||||
"name": "Int_LifeSupport_Size8_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1569565
|
||||
}
|
||||
},
|
||||
"MainEngines": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064094,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 253,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_engine_mass_curve_multiplier",
|
||||
"type": 1,
|
||||
"value": 0.098235413432121
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_heat",
|
||||
"type": 1,
|
||||
"value": 0.18069696426392
|
||||
},
|
||||
{
|
||||
"name": "mod_passive_power",
|
||||
"type": 1,
|
||||
"value": 0.033788848668337
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.056404989212751
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_mass_curve",
|
||||
"type": 1,
|
||||
"value": -0.027384582906961
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_heat",
|
||||
"type": 2,
|
||||
"value": -0.072683908045292
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
17
|
||||
],
|
||||
"recipeID": 128673655,
|
||||
"slotIndex": 52
|
||||
},
|
||||
"name": "Int_Engine_Size7_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"recipeLevel": 1,
|
||||
"recipeName": "Engine_Dirty",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1709638
|
||||
}
|
||||
},
|
||||
"MediumHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049436,
|
||||
"name": "Hpt_BeamLaser_Turret_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1889910
|
||||
}
|
||||
},
|
||||
"MediumHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049436,
|
||||
"name": "Hpt_BeamLaser_Turret_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1889910
|
||||
}
|
||||
},
|
||||
"MediumHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"MediumHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"MediumHardpoint5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"PaintJob": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128732290,
|
||||
"name": "PaintJob_BelugaLiner_Tactical_White",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"PlanetaryApproachSuite": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672317,
|
||||
"name": "Int_PlanetApproachSuite",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 450,
|
||||
"value": 450
|
||||
}
|
||||
},
|
||||
"PowerDistributor": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064207,
|
||||
"name": "Int_PowerDistributor_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 3128120
|
||||
}
|
||||
},
|
||||
"PowerPlant": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064057,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 277,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_powerplant_power",
|
||||
"type": 1,
|
||||
"value": 0.054692290723324
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.033690698444843
|
||||
},
|
||||
{
|
||||
"name": "mod_powerplant_heat",
|
||||
"type": 1,
|
||||
"value": 0.027470717206597
|
||||
},
|
||||
{
|
||||
"name": "mod_powerplant_heat",
|
||||
"type": 2,
|
||||
"value": -0.056317910552025
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
18
|
||||
],
|
||||
"recipeID": 128673765,
|
||||
"slotIndex": 51
|
||||
},
|
||||
"name": "Int_Powerplant_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"recipeLevel": 1,
|
||||
"recipeName": "PowerPlant_Boosted",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 14561578
|
||||
}
|
||||
},
|
||||
"Radar": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064239,
|
||||
"name": "Int_Sensors_Size5_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 71500
|
||||
}
|
||||
},
|
||||
"Slot01_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666681,
|
||||
"name": "Int_FuelScoop_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 25887249
|
||||
}
|
||||
},
|
||||
"Slot02_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064287,
|
||||
"name": "Int_ShieldGenerator_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 14561578
|
||||
}
|
||||
},
|
||||
"Slot03_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727927,
|
||||
"name": "Int_PassengerCabin_Size6_Class2",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 165808,
|
||||
"value": 165808
|
||||
}
|
||||
},
|
||||
"Slot04_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727928,
|
||||
"name": "Int_PassengerCabin_Size6_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 497429
|
||||
}
|
||||
},
|
||||
"Slot05_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727925,
|
||||
"name": "Int_PassengerCabin_Size5_Class4",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1492286
|
||||
}
|
||||
},
|
||||
"Slot06_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 100409,
|
||||
"value": 100409
|
||||
}
|
||||
},
|
||||
"Slot07_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727922,
|
||||
"name": "Int_PassengerCabin_Size4_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 17059
|
||||
}
|
||||
},
|
||||
"Slot08_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667632,
|
||||
"name": "Int_Repairer_Size3_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 2361960
|
||||
}
|
||||
},
|
||||
"Slot09_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672289,
|
||||
"name": "Int_BuggyBay_Size2_Class2",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 19440
|
||||
}
|
||||
},
|
||||
"Slot10_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666634,
|
||||
"name": "Int_DetailedSurfaceScanner_Tiny",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 225000
|
||||
}
|
||||
},
|
||||
"Slot11_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128663561,
|
||||
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1390500
|
||||
}
|
||||
},
|
||||
"TinyHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049513,
|
||||
"name": "Hpt_ChaffLauncher_Tiny",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 7650
|
||||
}
|
||||
},
|
||||
"TinyHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 252900
|
||||
}
|
||||
},
|
||||
"TinyHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 252900
|
||||
}
|
||||
},
|
||||
"TinyHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"WeaponColour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128732194,
|
||||
"name": "WeaponCustomisation_Purple",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "BelugaLiner",
|
||||
"value": {
|
||||
"hull": 71688743,
|
||||
"modules": 120812762,
|
||||
"unloaned": 1869489
|
||||
}
|
||||
}
|
||||
314
__tests__/fixtures/companion-api-import-3.json
Normal file
314
__tests__/fixtures/companion-api-import-3.json
Normal file
@@ -0,0 +1,314 @@
|
||||
{
|
||||
"cargo": {
|
||||
"capacity": 264
|
||||
},
|
||||
"free": false,
|
||||
"fuel": {
|
||||
"main": {
|
||||
"capacity": 32
|
||||
},
|
||||
"reserve": {
|
||||
"capacity": 0.52
|
||||
}
|
||||
},
|
||||
"id": 4,
|
||||
"modules": {
|
||||
"Armour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049298,
|
||||
"name": "Type7_Armour_Grade1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Bobble01": [],
|
||||
"Bobble02": [],
|
||||
"Bobble03": [],
|
||||
"Bobble04": [],
|
||||
"Bobble05": [],
|
||||
"Bobble06": [],
|
||||
"Bobble07": [],
|
||||
"Bobble08": [],
|
||||
"Bobble09": [],
|
||||
"Bobble10": [],
|
||||
"Decal1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667746,
|
||||
"name": "Decal_Trade_Dealer",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667738,
|
||||
"name": "Decal_Combat_Competent",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667753,
|
||||
"name": "Decal_Explorer_Scout",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"EngineColour": [],
|
||||
"FrameShiftDrive": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064122,
|
||||
"name": "Int_Hyperdrive_Size5_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 5103953
|
||||
}
|
||||
},
|
||||
"FuelTank": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064350,
|
||||
"name": "Int_FuelTank_Size5_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 97754,
|
||||
"value": 97754
|
||||
}
|
||||
},
|
||||
"LifeSupport": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064154,
|
||||
"name": "Int_LifeSupport_Size4_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 28373
|
||||
}
|
||||
},
|
||||
"MainEngines": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064087,
|
||||
"name": "Int_Engine_Size5_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 5103953
|
||||
}
|
||||
},
|
||||
"PaintJob": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128671422,
|
||||
"name": "PaintJob_Type7_Tactical_White",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"PlanetaryApproachSuite": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672317,
|
||||
"name": "Int_PlanetApproachSuite",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 500,
|
||||
"value": 500
|
||||
}
|
||||
},
|
||||
"PowerDistributor": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064192,
|
||||
"name": "Int_PowerDistributor_Size3_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 158331
|
||||
}
|
||||
},
|
||||
"PowerPlant": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064047,
|
||||
"name": "Int_Powerplant_Size4_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1610080
|
||||
}
|
||||
},
|
||||
"Radar": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064229,
|
||||
"name": "Int_Sensors_Size3_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 10133
|
||||
}
|
||||
},
|
||||
"Slot01_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot02_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot03_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot04_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 111566,
|
||||
"value": 111566
|
||||
}
|
||||
},
|
||||
"Slot05_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 111566,
|
||||
"value": 111566
|
||||
}
|
||||
},
|
||||
"Slot06_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064279,
|
||||
"name": "Int_ShieldGenerator_Size5_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 189035
|
||||
}
|
||||
},
|
||||
"Slot07_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049549,
|
||||
"name": "Int_DockingComputer_Standard",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 4500
|
||||
}
|
||||
},
|
||||
"Slot08_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064340,
|
||||
"name": "Int_CargoRack_Size3_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 10563
|
||||
}
|
||||
},
|
||||
"SmallHardpoint1": [],
|
||||
"SmallHardpoint2": [],
|
||||
"SmallHardpoint3": [],
|
||||
"SmallHardpoint4": [],
|
||||
"TinyHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049513,
|
||||
"name": "Hpt_ChaffLauncher_Tiny",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 8500
|
||||
}
|
||||
},
|
||||
"WeaponColour": []
|
||||
},
|
||||
"name": "Type7",
|
||||
"value": {
|
||||
"hull": 16780009,
|
||||
"modules": 14479580,
|
||||
"unloaned": 321386
|
||||
}
|
||||
}
|
||||
327
__tests__/fixtures/courier-test-detailed-export-v4.json
Normal file
327
__tests__/fixtures/courier-test-detailed-export-v4.json
Normal file
@@ -0,0 +1,327 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Multi-purpose Imperial Courier",
|
||||
"ship": "Imperial Courier",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "https://coriolis.edcd.io/outfit/imperial_courier?code=0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier",
|
||||
"code": "0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ=.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s/kIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA+/z+z3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ+LgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ/0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g/6v0TTdbxJ2KhdEIaCw15MF/NB0L+S2hwEwyFM8KgP+qEpWWA3Qu9Z3z9kPWHzakt7Dt+AeD7ghSTgEAAA==",
|
||||
"shipId": "imperial_courier"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Lightweight Alloy",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"modifications": {
|
||||
"pgen": 1052,
|
||||
"integrity": -482,
|
||||
"eff": 974
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 63,
|
||||
"name": "Overcharged",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"name": "Enhanced Performance",
|
||||
"modifications": {
|
||||
"optmul": 2476,
|
||||
"thermload": 7023,
|
||||
"power": 1763,
|
||||
"integrity": 165,
|
||||
"optmass": -667
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 22,
|
||||
"name": "Dirty",
|
||||
"grade": 4
|
||||
}
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": 4082,
|
||||
"integrity": -2422,
|
||||
"power": 1782,
|
||||
"optmass": 4927
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 26,
|
||||
"name": "Increased range",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 1,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 2,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 3,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Pulse Laser",
|
||||
"mount": "Fixed",
|
||||
"modifications": {
|
||||
"rof": 5931,
|
||||
"damage": -184,
|
||||
"jitter": 50,
|
||||
"distdraw": -4689,
|
||||
"piercing": 3328
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 89,
|
||||
"name": "Rapid fire",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Pulse Laser",
|
||||
"mount": "Fixed",
|
||||
"modifications": {
|
||||
"rof": 4715,
|
||||
"damage": -97,
|
||||
"jitter": 30,
|
||||
"distdraw": -4548,
|
||||
"piercing": 1057,
|
||||
"integrity": 319
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 89,
|
||||
"name": "Rapid fire",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Multi-cannon",
|
||||
"mount": "Gimballed",
|
||||
"modifications": {
|
||||
"damage": 2437,
|
||||
"distdraw": 5487,
|
||||
"rof": 1120,
|
||||
"jitter": 58,
|
||||
"thermload": 1346,
|
||||
"power": 1009,
|
||||
"integrity": -202,
|
||||
"ammo": -2000
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 88,
|
||||
"name": "Overcharged",
|
||||
"grade": 3,
|
||||
"special": {
|
||||
"id": 3,
|
||||
"name": "Corrosive shell"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher",
|
||||
"modifications": {
|
||||
"ammo": 5000,
|
||||
"mass": 17684,
|
||||
"reload": 9707
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 37,
|
||||
"name": "Ammo capacity",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher",
|
||||
"modifications": {
|
||||
"ammo": 5000,
|
||||
"mass": 18520,
|
||||
"reload": 8715
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 37,
|
||||
"name": "Ammo capacity",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Chaff Launcher",
|
||||
"name": "Chaff Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Frame Shift Wake Scanner"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator",
|
||||
"modifications": {
|
||||
"optmul": 1888,
|
||||
"explres": 455,
|
||||
"kinres": 546,
|
||||
"thermres": 1092,
|
||||
"brokenregen": -2614,
|
||||
"regen": -876,
|
||||
"distdraw": 463
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 77,
|
||||
"name": "Reinforced",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Advanced Discovery Scanner"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 1,
|
||||
"hullCost": 2481550,
|
||||
"speed": 280,
|
||||
"boost": 380,
|
||||
"boostEnergy": 10,
|
||||
"agility": 6,
|
||||
"baseShieldStrength": 200,
|
||||
"baseArmour": 80,
|
||||
"hullMass": 35,
|
||||
"masslock": 7,
|
||||
"pipSpeed": 0.05,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 8,
|
||||
"cargoCapacity": 8,
|
||||
"ladenMass": 104.25,
|
||||
"armour": 144,
|
||||
"shield": 404.19,
|
||||
"shieldCells": 0,
|
||||
"totalCost": 14059860,
|
||||
"unladenMass": 88.25,
|
||||
"totalDpe": 32.25,
|
||||
"totalExplDpe": 0,
|
||||
"totalKinDpe": 9.41,
|
||||
"totalThermDpe": 22.84,
|
||||
"totalDps": 53.8,
|
||||
"totalExplDps": 0,
|
||||
"totalKinDps": 17.44,
|
||||
"totalThermDps": 36.35,
|
||||
"totalSDps": 48.99,
|
||||
"totalExplSDps": 0,
|
||||
"totalKinSDps": 12.64,
|
||||
"totalThermSDps": 36.35,
|
||||
"totalEps": 9.84,
|
||||
"totalHps": 12.28,
|
||||
"shieldExplRes": 0.48,
|
||||
"shieldKinRes": 0.55,
|
||||
"shieldThermRes": 1.09,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"powerAvailable": 17.24,
|
||||
"powerRetracted": 11.3,
|
||||
"powerDeployed": 16.41,
|
||||
"unladenRange": 25.57,
|
||||
"fullTankRange": 23.92,
|
||||
"ladenRange": 22.09,
|
||||
"unladenFastestRange": 116.23,
|
||||
"ladenFastestRange": 107,
|
||||
"maxJumpCount": 5,
|
||||
"topSpeed": 386.56,
|
||||
"topBoost": 524.62
|
||||
}
|
||||
}
|
||||
@@ -2,31 +2,31 @@
|
||||
{
|
||||
"shipId": "anaconda",
|
||||
"buildName": "Imported Anaconda",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
|
||||
"buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
|
||||
},
|
||||
{
|
||||
"shipId": "anaconda",
|
||||
"buildName": "Imported Anaconda",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
|
||||
"buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
|
||||
},
|
||||
{
|
||||
"shipId": "cobra_mk_iii",
|
||||
"buildName": "Imported Cobra Mk III",
|
||||
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==",
|
||||
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==.",
|
||||
"buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n"
|
||||
},
|
||||
{
|
||||
"shipId": "type_9_heavy",
|
||||
"buildName": "Imported Type-9 Heavy",
|
||||
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==",
|
||||
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==.",
|
||||
"buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n"
|
||||
},
|
||||
{
|
||||
"shipId": "vulture",
|
||||
"buildName": "Imported Vulture",
|
||||
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==",
|
||||
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==.",
|
||||
"buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
{
|
||||
"type_6_transporter": {
|
||||
"Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
|
||||
"Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
|
||||
"Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
|
||||
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
|
||||
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
|
||||
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
|
||||
},
|
||||
"type_7_transport": {
|
||||
"Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
|
||||
"Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
|
||||
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
|
||||
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
|
||||
},
|
||||
"federal_dropship": {
|
||||
"Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
|
||||
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"asp": {
|
||||
"Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
|
||||
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
|
||||
},
|
||||
"imperial_clipper": {
|
||||
"Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
|
||||
"Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
|
||||
"Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
|
||||
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
|
||||
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
|
||||
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
|
||||
},
|
||||
"type_9_heavy": {
|
||||
"Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
|
||||
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
|
||||
},
|
||||
"python": {
|
||||
"Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
|
||||
"Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
|
||||
"Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
|
||||
"Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
|
||||
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
|
||||
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
|
||||
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
|
||||
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"anaconda": {
|
||||
"Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
|
||||
"Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
|
||||
"Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
|
||||
"Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
|
||||
"Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
|
||||
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
|
||||
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
|
||||
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
|
||||
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
|
||||
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
|
||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
|
||||
},
|
||||
"vulture": {
|
||||
"Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
|
||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
||||
},
|
||||
"fer_de_lance": {
|
||||
"Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
|
||||
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
|
||||
},
|
||||
"eagle": {
|
||||
"Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
|
||||
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
},
|
||||
"anaconda": {
|
||||
"Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=",
|
||||
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA="
|
||||
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=",
|
||||
"Modified": "0pyttFolodDsyf5------1717--------05044j-03----2h00.Iw18ZlA=.Aw18ZlA=.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA=="
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
|
||||
@@ -63,4 +64,4 @@
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
90
__tests__/test-agility.js
Normal file
90
__tests__/test-agility.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import Ship from '../src/app/shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
|
||||
|
||||
describe("Agility", function() {
|
||||
|
||||
it("correctly calculates speed", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.topSpeed)).toBe(thrusterData.speed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates boost", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
// Turn off internals to ensure we have enough power to boost
|
||||
for (let internal in ship.internal) {
|
||||
ship.internal[internal].enabled = 0;
|
||||
}
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.topBoost)).toBe(thrusterData.boost);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates pitch", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.pitches[4] * 100) / 100).toBeCloseTo(thrusterData.pitch, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates roll", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.rolls[4] * 100) / 100).toBeCloseTo(thrusterData.roll, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates yaw", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.yaws[4] * 100) / 100).toBeCloseTo(thrusterData.yaw, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -108,7 +108,8 @@ 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
|
||||
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
|
||||
delete(invalidImportData.builds.asp);
|
||||
|
||||
pasteText('"this is not valid"');
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
@@ -128,7 +129,7 @@ describe('Import Modal', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed Build', function() {
|
||||
describe('Import Detailed V3 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
@@ -141,18 +142,64 @@ 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/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship');
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
|
||||
});
|
||||
|
||||
it('catches an invalid build', function() {
|
||||
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
|
||||
pasteText(JSON.stringify(importData).replace('components', 'comps'));
|
||||
pasteText(JSON.stringify(importData).replace('references', 'refs'));
|
||||
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed V4 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
const importData = require('./fixtures/anaconda-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed Engineered V4 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
const importData = require('./fixtures/asp-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer');
|
||||
});
|
||||
|
||||
it('imports a valid v4 build with modifications', function() {
|
||||
const importData = require('./fixtures/courier-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detaild Builds Array', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
@@ -178,22 +225,63 @@ describe('Import Modal', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Companion API Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-1');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA02SPy9DURjG3%2F65vW1v47TXVbeqqF7EQtIIBomRJswsYmISH8BgkFhqFZ9AwlALMYitkXQyEF2k4SMYJNK0dV7PK7nc5ck55%2Fm9z%2FnznpBeJqLvECQbM4hUjZnjO5hyWGfFikAGGjGiku0QuddhQCNdZmdWM9snsDmih4REOdlnNvz9DrPrJIicPdSwoZf8pAnTIpq8x7DYADS%2Bi5DERY85%2BYqpmkc6x%2FWGf6beKCR3YBIZFZCxCgrtczjuOmo4qTf94F4KYuxhz5jjEhXmUJNexFrpIUo02ALN1j9u1JMgD%2FMga1GfbMNRd9iHUwGy%2BpspZF3IBSGvMFJluS%2FuR24FJ2KlV%2Fxju6sQq4lhRsQTUVUJTgegLtS6EUjEE1HPAmUC0KdAjwKJeCKqD8zoURx72gHyDW9nvQhJGHkyUscS1x%2BAZnAlqwU%2FI%2BKJKEvextXrf93eQrR1KUlS5HWwGC61mfOn0oN3IM4OHoBzuuIHj33hS5jT8KeamIYa0sjhgH%2BLfplP4kcwD5Xl3xR1wfeHtqWzBHHX8I9SH9Je%2FgGvXxeungIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-2');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-3');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import E:D Shipyard Builds', function() {
|
||||
|
||||
it('imports a valid builds', function() {
|
||||
const imports = require('./fixtures/ed-shipyard-import-valid');
|
||||
|
||||
for (let i = 0; i < imports.length; i++ ) {
|
||||
reset();
|
||||
let fixture = imports[i];
|
||||
pasteText(fixture.buildText);
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '/' + fixture.buildCode + '?bn=' + encodeURIComponent(fixture.buildName));
|
||||
}
|
||||
});
|
||||
// it('imports a valid build', function() {
|
||||
// const imports = require('./fixtures/ed-shipyard-import-valid');
|
||||
//
|
||||
// for (let i = 0; i < imports.length; i++ ) {
|
||||
// reset();
|
||||
// let fixture = imports[i];
|
||||
// pasteText(fixture.buildText);
|
||||
// expect(modal.state.importValid).toBeTruthy();
|
||||
// expect(modal.state.errorMsg).toEqual(null);
|
||||
// clickProceed();
|
||||
// expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
// expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '?code=' + encodeURIComponent(fixture.buildCode) + '&bn=' + encodeURIComponent(fixture.buildName));
|
||||
// }
|
||||
// });
|
||||
|
||||
it('catches invalid builds', function() {
|
||||
const imports = require('./fixtures/ed-shipyard-import-invalid');
|
||||
|
||||
@@ -4,16 +4,16 @@ import * as Serializer from '../src/app/shipyard/Serializer';
|
||||
import jsen from 'jsen';
|
||||
|
||||
describe("Serializer", function() {
|
||||
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v3');
|
||||
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v4');
|
||||
const code = anacondaTestExport.references[0].code;
|
||||
const anaconda = Ships.anaconda;
|
||||
const validate = jsen(require('../src/schemas/ship-loadout/3'));
|
||||
const validate = jsen(require('../src/schemas/ship-loadout/4'));
|
||||
|
||||
describe("To Detailed Build", function() {
|
||||
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
|
||||
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
|
||||
|
||||
it("conforms to the v3 ship-loadout schema", function() {
|
||||
it("conforms to the v4 ship-loadout schema", function() {
|
||||
expect(validate(exportData)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("Serializer", function() {
|
||||
const builds = require('./fixtures/expected-builds');
|
||||
const exportData = Serializer.toDetailedExport(builds);
|
||||
|
||||
it("conforms to the v3 ship-loadout schema", function() {
|
||||
it("conforms to the v4 ship-loadout schema", function() {
|
||||
expect(exportData instanceof Array).toBe(true);
|
||||
|
||||
for (let detailedBuild of exportData) {
|
||||
|
||||
@@ -16,7 +16,7 @@ describe("Ship", function() {
|
||||
ship.buildWith(shipData.defaults);
|
||||
|
||||
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
|
||||
expect(ship.cargoCapacity).toBeDefined(s + ' cargo');
|
||||
expect(ship.cargoCapacity).toBeDefined();
|
||||
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
|
||||
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
|
||||
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
|
||||
@@ -24,7 +24,7 @@ describe("Ship", function() {
|
||||
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
|
||||
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
|
||||
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
|
||||
expect(ship.shieldStrength).toBeGreaterThan(0, s + ' shieldStrength');
|
||||
expect(ship.shield).toBeGreaterThan(0, s + ' shield');
|
||||
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
|
||||
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
|
||||
}
|
||||
|
||||
11
d3-funcs.js
vendored
Normal file
11
d3-funcs.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export {
|
||||
axisBottom,
|
||||
axisLeft,
|
||||
axisTop,
|
||||
formatLocale,
|
||||
line,
|
||||
scaleBand,
|
||||
scaleLinear,
|
||||
scaleOrdinal,
|
||||
select
|
||||
} from 'd3';
|
||||
4
d3.min.js
vendored
Normal file
4
d3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -11,6 +11,15 @@ new WebpackDevServer(webpack(config), {
|
||||
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
|
||||
{ from: /\/outfit\//, to: '/index.html' }
|
||||
]
|
||||
},
|
||||
stats: {
|
||||
assets: true,
|
||||
colors: true,
|
||||
version: false,
|
||||
hash: false,
|
||||
timings: true,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}
|
||||
}).listen(3300, "0.0.0.0", function (err, result) {
|
||||
if (err) {
|
||||
|
||||
43
package.json
43
package.json
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.0.0",
|
||||
"version": "2.3.3",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cmmcleod/coriolis"
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.io",
|
||||
"bugs": "https://github.com/cmmcleod/coriolis/issues",
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
@@ -18,21 +19,22 @@
|
||||
"test": "jest",
|
||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||
"build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js",
|
||||
"build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||
},
|
||||
"jest": {
|
||||
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
|
||||
"testFileExtensions": [
|
||||
"js"
|
||||
],
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-addons-test-utils",
|
||||
@@ -52,7 +54,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"appcache-webpack-plugin": "^1.2.1",
|
||||
"appcache-webpack-plugin": "^1.3.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "*",
|
||||
"babel-jest": "*",
|
||||
@@ -61,35 +63,44 @@
|
||||
"babel-preset-react": "*",
|
||||
"babel-preset-stage-0": "*",
|
||||
"css-loader": "^0.23.0",
|
||||
"d3-selection": "1",
|
||||
"eslint": "2.2.0",
|
||||
"eslint-plugin-react": "^4.0.0",
|
||||
"expose-loader": "^0.7.1",
|
||||
"express": "^4.13.3",
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"extract-text-webpack-plugin": "2.0.0",
|
||||
"file-loader": "^0.8.4",
|
||||
"html-webpack-plugin": "^1.7.0",
|
||||
"jest-cli": "^0.9.2",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jest-cli": "^16.0.1",
|
||||
"jsen": "^0.6.0",
|
||||
"json-loader": "^0.5.3",
|
||||
"less": "^2.5.3",
|
||||
"less-loader": "^2.2.1",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-addons-test-utils": "^15.0.1",
|
||||
"react-measure": "^1.4.6",
|
||||
"react-testutils-additions": "^15.1.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"rollup": "0.36",
|
||||
"rollup-plugin-node-resolve": "2",
|
||||
"style-loader": "^0.13.0",
|
||||
"url-loader": "^0.5.6",
|
||||
"webpack": "^1.9.6",
|
||||
"webpack-dev-server": "^1.14.0"
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-dev-server": "^2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"browserify-zlib": "ipfs/browserify-zlib",
|
||||
"classnames": "^2.2.0",
|
||||
"coriolis-data": "cmmcleod/coriolis-data",
|
||||
"d3": "3.5.16",
|
||||
"coriolis-data": "EDCD/coriolis-data",
|
||||
"d3": "4.6.0",
|
||||
"fbemitter": "^2.0.0",
|
||||
"lodash": "^4.15.0",
|
||||
"lz-string": "^1.4.4",
|
||||
"react": "^15.0.1",
|
||||
"react-dom": "^15.0.1",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^0.21.2",
|
||||
"superagent": "^1.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
9
rollup.config.js
Normal file
9
rollup.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import nodeResolve from "rollup-plugin-node-resolve";
|
||||
|
||||
export default {
|
||||
entry: "d3-funcs.js",
|
||||
format: "umd",
|
||||
moduleName: "d3",
|
||||
plugins: [nodeResolve({jsnext: true})],
|
||||
dest: "d3.js"
|
||||
};
|
||||
906
src/.htaccess
Normal file
906
src/.htaccess
Normal file
@@ -0,0 +1,906 @@
|
||||
RewriteEngine on
|
||||
|
||||
# If a directory or a file exists, use the request directly
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# Otherwise forward the request to index.php
|
||||
RewriteRule . index.html
|
||||
|
||||
# Apache Server Configs v2.10.0 | MIT License
|
||||
# https://github.com/h5bp/server-configs-apache
|
||||
|
||||
# (!) Using `.htaccess` files slows down Apache, therefore, if you have
|
||||
# access to the main server configuration file (which is usually called
|
||||
# `httpd.conf`), you should add this logic there.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/howto/htaccess.html.
|
||||
|
||||
# ######################################################################
|
||||
# # CROSS-ORIGIN #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Cross-origin requests |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow cross-origin requests.
|
||||
#
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
|
||||
# http://enable-cors.org/
|
||||
# http://www.w3.org/TR/cors/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Access-Control-Allow-Origin "*"
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Cross-origin images |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Send the CORS header for images when browsers request it.
|
||||
#
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
|
||||
# https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(bmp|cur|gif|ico|jpe?g|png|svgz?|webp)$">
|
||||
SetEnvIf Origin ":" IS_CORS
|
||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Cross-origin web fonts |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow cross-origin access to web fonts.
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(eot|otf|tt[cf]|woff2?)$">
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Cross-origin resource timing |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow cross-origin access to the timing information for all resources.
|
||||
#
|
||||
# If a resource isn't served with a `Timing-Allow-Origin` header that
|
||||
# would allow its timing information to be shared with the document,
|
||||
# some of the attributes of the `PerformanceResourceTiming` object will
|
||||
# be set to zero.
|
||||
#
|
||||
# http://www.w3.org/TR/resource-timing/
|
||||
# http://www.stevesouders.com/blog/2014/08/21/resource-timing-practical-tips/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Timing-Allow-Origin: "*"
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # ERRORS #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Custom error messages/pages |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Customize what Apache returns to the client in case of an error.
|
||||
# https://httpd.apache.org/docs/current/mod/core.html#errordocument
|
||||
|
||||
ErrorDocument 404 /404.html
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Error prevention |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Disable the pattern matching based on filenames.
|
||||
#
|
||||
# This setting prevents Apache from returning a 404 error as the result
|
||||
# of a rewrite when the directory with the same name does not exist.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/content-negotiation.html#multiviews
|
||||
|
||||
Options -MultiViews
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # INTERNET EXPLORER #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Document modes |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Force Internet Explorer 8/9/10 to render pages in the highest mode
|
||||
# available in the various cases when it may not.
|
||||
#
|
||||
# https://hsivonen.fi/doctype/#ie8
|
||||
#
|
||||
# (!) Starting with Internet Explorer 11, document modes are deprecated.
|
||||
# If your business still relies on older web apps and services that were
|
||||
# designed for older versions of Internet Explorer, you might want to
|
||||
# consider enabling `Enterprise Mode` throughout your company.
|
||||
#
|
||||
# http://msdn.microsoft.com/en-us/library/ie/bg182625.aspx#docmode
|
||||
# http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-UA-Compatible "IE=edge"
|
||||
# `mod_headers` cannot match based on the content-type, however,
|
||||
# the `X-UA-Compatible` response header should be send only for
|
||||
# HTML documents and not for the other resources.
|
||||
<FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
|
||||
Header unset X-UA-Compatible
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Iframes cookies |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow cookies to be set from iframes in Internet Explorer.
|
||||
#
|
||||
# http://msdn.microsoft.com/en-us/library/ms537343.aspx
|
||||
# http://www.w3.org/TR/2000/CR-P3P-20001215/
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # MEDIA TYPES AND CHARACTER ENCODINGS #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Media types |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve resources with the proper media types (f.k.a. MIME types).
|
||||
#
|
||||
# https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
|
||||
# Data interchange
|
||||
AddType application/json json map topojson
|
||||
AddType application/ld+json jsonld
|
||||
AddType application/vnd.geo+json geojson
|
||||
AddType application/xml atom rdf rss xml
|
||||
|
||||
# JavaScript
|
||||
# Normalize to standard type.
|
||||
# https://tools.ietf.org/html/rfc4329#section-7.2
|
||||
AddType application/javascript js
|
||||
|
||||
# Manifest files
|
||||
|
||||
# If you are providing a web application manifest file (see
|
||||
# the specification: https://w3c.github.io/manifest/), it is
|
||||
# recommended that you serve it with the `application/manifest+json`
|
||||
# media type.
|
||||
#
|
||||
# Because the web application manifest file doesn't have its
|
||||
# own unique file extension, you can set its media type either
|
||||
# by matching:
|
||||
#
|
||||
# 1) the exact location of the file (this can be done using a
|
||||
# directive such as `<Location>`, but it will NOT work in
|
||||
# the `.htaccess` file, so you will have to do it in the main
|
||||
# server configuration file or inside of a `<VirtualHost>`
|
||||
# container)
|
||||
#
|
||||
# e.g.:
|
||||
#
|
||||
# <Location "/.well-known/manifest.json">
|
||||
# AddType application/manifest+json json
|
||||
# </Location>
|
||||
#
|
||||
# 2) the filename (this can be problematic as you will need to
|
||||
# ensure that you don't have any other file with the same name
|
||||
# as the one you gave to your web application manifest file)
|
||||
#
|
||||
# e.g.:
|
||||
#
|
||||
# <Files "manifest.json">
|
||||
# AddType application/manifest+json json
|
||||
# </Files>
|
||||
|
||||
AddType application/x-web-app-manifest+json webapp
|
||||
AddType text/cache-manifest appcache manifest
|
||||
|
||||
# Media files
|
||||
AddType audio/mp4 f4a f4b m4a
|
||||
AddType audio/ogg oga ogg opus
|
||||
AddType image/bmp bmp
|
||||
AddType image/webp webp
|
||||
AddType image/x-icon cur ico
|
||||
AddType video/mp4 f4v f4p m4v mp4
|
||||
AddType video/ogg ogv
|
||||
AddType video/webm webm
|
||||
AddType video/x-flv flv
|
||||
AddType image/svg+xml svg svgz
|
||||
|
||||
# Web fonts
|
||||
AddType application/font-woff woff
|
||||
AddType application/font-woff2 woff2
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
|
||||
# Browsers usually ignore the font media types and simply sniff
|
||||
# the bytes to figure out the font type.
|
||||
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
|
||||
#
|
||||
# However, Blink and WebKit based browsers will show a warning
|
||||
# in the console if the following font types are served with any
|
||||
# other media types.
|
||||
|
||||
AddType application/x-font-ttf ttc ttf
|
||||
AddType font/opentype otf
|
||||
|
||||
# Other
|
||||
AddType application/octet-stream safariextz
|
||||
AddType application/x-bb-appworld bbaw
|
||||
AddType application/x-chrome-extension crx
|
||||
AddType application/x-opera-extension oex
|
||||
AddType application/x-xpinstall xpi
|
||||
AddType text/vcard vcard vcf
|
||||
AddType text/vnd.rim.location.xloc xloc
|
||||
AddType text/vtt vtt
|
||||
AddType text/x-component htc
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Character encodings |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve all resources labeled as `text/html` or `text/plain`
|
||||
# with the media type `charset` parameter set to `UTF-8`.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
|
||||
|
||||
AddDefaultCharset utf-8
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Serve the following file types with the media type `charset`
|
||||
# parameter set to `UTF-8`.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddCharset utf-8 .atom \
|
||||
.bbaw \
|
||||
.css \
|
||||
.geojson \
|
||||
.js \
|
||||
.json \
|
||||
.jsonld \
|
||||
.rdf \
|
||||
.rss \
|
||||
.topojson \
|
||||
.vtt \
|
||||
.webapp \
|
||||
.xloc \
|
||||
.xml
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # REWRITES #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Rewrite engine |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# (1) Turn on the rewrite engine (this is necessary in order for
|
||||
# the `RewriteRule` directives to work).
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteEngine
|
||||
#
|
||||
# (2) Enable the `FollowSymLinks` option if it isn't already.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/core.html#options
|
||||
#
|
||||
# (3) If your web host doesn't allow the `FollowSymlinks` option,
|
||||
# you need to comment it out or remove it, and then uncomment
|
||||
# the `Options +SymLinksIfOwnerMatch` line (4), but be aware
|
||||
# of the performance impact.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
|
||||
#
|
||||
# (4) Some cloud hosting services will require you set `RewriteBase`.
|
||||
#
|
||||
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-modrewrite-not-working-on-my-site
|
||||
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
|
||||
#
|
||||
# (5) Depending on how your server is set up, you may also need to
|
||||
# use the `RewriteOptions` directive to enable some options for
|
||||
# the rewrite engine.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriteoptions
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
# (1)
|
||||
RewriteEngine On
|
||||
|
||||
# (2)
|
||||
Options +FollowSymlinks
|
||||
|
||||
# (3)
|
||||
# Options +SymLinksIfOwnerMatch
|
||||
|
||||
# (4)
|
||||
# RewriteBase /
|
||||
|
||||
# (5)
|
||||
# RewriteOptions <options>
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Forcing `https://` |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Redirect from the `http://` to the `https://` version of the URL.
|
||||
# https://wiki.apache.org/httpd/RewriteHTTPToHTTPS
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{HTTPS} !=on
|
||||
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Suppressing / Forcing the `www.` at the beginning of URLs |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# The same content should never be available under two different
|
||||
# URLs, especially not with and without `www.` at the beginning.
|
||||
# This can cause SEO problems (duplicate content), and therefore,
|
||||
# you should choose one of the alternatives and redirect the other
|
||||
# one.
|
||||
#
|
||||
# By default `Option 1` (no `www.`) is activated.
|
||||
# http://no-www.org/faq.php?q=class_b
|
||||
#
|
||||
# If you would prefer to use `Option 2`, just comment out all the
|
||||
# lines from `Option 1` and uncomment the ones from `Option 2`.
|
||||
#
|
||||
# (!) NEVER USE BOTH RULES AT THE SAME TIME!
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 1: rewrite www.example.com → example.com
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} !=on
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Option 2: rewrite example.com → www.example.com
|
||||
#
|
||||
# Be aware that the following might not be a good idea if you use "real"
|
||||
# subdomains for certain parts of your website.
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{HTTPS} !=on
|
||||
# RewriteCond %{HTTP_HOST} !^www\. [NC]
|
||||
# RewriteCond %{SERVER_ADDR} !=127.0.0.1
|
||||
# RewriteCond %{SERVER_ADDR} !=::1
|
||||
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
# </IfModule>
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # SECURITY #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Clickjacking |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Protect website against clickjacking.
|
||||
#
|
||||
# The example below sends the `X-Frame-Options` response header with
|
||||
# the value `DENY`, informing browsers not to display the content of
|
||||
# the web page in any frame.
|
||||
#
|
||||
# This might not be the best setting for everyone. You should read
|
||||
# about the other two possible values the `X-Frame-Options` header
|
||||
# field can have: `SAMEORIGIN` and `ALLOW-FROM`.
|
||||
# https://tools.ietf.org/html/rfc7034#section-2.1.
|
||||
#
|
||||
# Keep in mind that while you could send the `X-Frame-Options` header
|
||||
# for all of your website’s pages, this has the potential downside that
|
||||
# it forbids even non-malicious framing of your content (e.g.: when
|
||||
# users visit your website using a Google Image Search results page).
|
||||
#
|
||||
# Nonetheless, you should ensure that you send the `X-Frame-Options`
|
||||
# header for all pages that allow a user to make a state changing
|
||||
# operation (e.g: pages that contain one-click purchase links, checkout
|
||||
# or bank-transfer confirmation pages, pages that make permanent
|
||||
# configuration changes, etc.).
|
||||
#
|
||||
# Sending the `X-Frame-Options` header can also protect your website
|
||||
# against more than just clickjacking attacks:
|
||||
# https://cure53.de/xfo-clickjacking.pdf.
|
||||
#
|
||||
# https://tools.ietf.org/html/rfc7034
|
||||
# http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
|
||||
# https://www.owasp.org/index.php/Clickjacking
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set X-Frame-Options "DENY"
|
||||
# # `mod_headers` cannot match based on the content-type, however,
|
||||
# # the `X-Frame-Options` response header should be send only for
|
||||
# # HTML documents and not for the other resources.
|
||||
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
|
||||
# Header unset X-Frame-Options
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Content Security Policy (CSP) |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Mitigate the risk of cross-site scripting and other content-injection
|
||||
# attacks.
|
||||
#
|
||||
# This can be done by setting a `Content Security Policy` which
|
||||
# whitelists trusted sources of content for your website.
|
||||
#
|
||||
# The example header below allows ONLY scripts that are loaded from the
|
||||
# current website's origin (no inline scripts, no CDN, etc). That almost
|
||||
# certainly won't work as-is for your website!
|
||||
#
|
||||
# For more details on how to craft a reasonable policy for your website,
|
||||
# read: http://www.html5rocks.com/en/tutorials/security/content-security-policy/
|
||||
# (or the specification: http://www.w3.org/TR/CSP11/). Also, to make
|
||||
# things easier, you can use an online CSP header generator such as:
|
||||
# http://cspisawesome.com/.
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
|
||||
# # `mod_headers` cannot match based on the content-type, however,
|
||||
# # the `Content-Security-Policy` response header should be send
|
||||
# # only for HTML documents and not for the other resources.
|
||||
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
|
||||
# Header unset Content-Security-Policy
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | File access |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Block access to directories without a default document.
|
||||
#
|
||||
# You should leave the following uncommented, as you shouldn't allow
|
||||
# anyone to surf through every directory on your server (which may
|
||||
# includes rather private places such as the CMS's directories).
|
||||
|
||||
<IfModule mod_autoindex.c>
|
||||
Options -Indexes
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to all hidden files and directories with the exception of
|
||||
# the visible content from within the `/.well-known/` hidden directory.
|
||||
#
|
||||
# These types of files usually contain user preferences or the preserved
|
||||
# state of an utility, and can include rather private places like, for
|
||||
# example, the `.git` or `.svn` directories.
|
||||
#
|
||||
# The `/.well-known/` directory represents the standard (RFC 5785) path
|
||||
# prefix for "well-known locations" (e.g.: `/.well-known/manifest.json`,
|
||||
# `/.well-known/keybase.txt`), and therefore, access to its visible
|
||||
# content should not be blocked.
|
||||
#
|
||||
# https://www.mnot.net/blog/2010/04/07/well-known
|
||||
# https://tools.ietf.org/html/rfc5785
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
|
||||
RewriteCond %{SCRIPT_FILENAME} -d [OR]
|
||||
RewriteCond %{SCRIPT_FILENAME} -f
|
||||
RewriteRule "(^|/)\." - [F]
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Block access to files that can expose sensitive information.
|
||||
#
|
||||
# By default, block access to backup and source files that may be
|
||||
# left by some text editors and can pose a security risk when anyone
|
||||
# has access to them.
|
||||
#
|
||||
# http://feross.org/cmsploit/
|
||||
#
|
||||
# (!) Update the `<FilesMatch>` regular expression from below to
|
||||
# include any files that might end up on your production server and
|
||||
# can expose sensitive information about your website. These files may
|
||||
# include: configuration files, files that contain metadata about the
|
||||
# project (e.g.: project dependencies), build scripts, etc..
|
||||
|
||||
<FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$">
|
||||
|
||||
# Apache < 2.3
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order allow,deny
|
||||
Deny from all
|
||||
Satisfy All
|
||||
</IfModule>
|
||||
|
||||
# Apache ≥ 2.3
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
|
||||
</FilesMatch>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | HTTP Strict Transport Security (HSTS) |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Force client-side SSL redirection.
|
||||
#
|
||||
# If a user types `example.com` in their browser, even if the server
|
||||
# redirects them to the secure version of the website, that still leaves
|
||||
# a window of opportunity (the initial HTTP connection) for an attacker
|
||||
# to downgrade or redirect the request.
|
||||
#
|
||||
# The following header ensures that browser will ONLY connect to your
|
||||
# server via HTTPS, regardless of what the users type in the browser's
|
||||
# address bar.
|
||||
#
|
||||
# (!) Remove the `includeSubDomains` optional directive if the website's
|
||||
# subdomains are not using HTTPS.
|
||||
#
|
||||
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
|
||||
# https://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1
|
||||
# http://blogs.msdn.com/b/ieinternals/archive/2014/08/18/hsts-strict-transport-security-attacks-mitigations-deployment-https.aspx
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header set Strict-Transport-Security "max-age=16070400; includeSubDomains"
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Reducing MIME type security risks |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Prevent some browsers from MIME-sniffing the response.
|
||||
#
|
||||
# This reduces exposure to drive-by download attacks and cross-origin
|
||||
# data leaks, and should be left uncommented, especially if the server
|
||||
# is serving user-uploaded content or content that could potentially be
|
||||
# treated as executable by the browser.
|
||||
#
|
||||
# http://www.slideshare.net/hasegawayosuke/owasp-hasegawa
|
||||
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
|
||||
# http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx
|
||||
# https://mimesniff.spec.whatwg.org/
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Reflected Cross-Site Scripting (XSS) attacks |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# (1) Try to re-enable the cross-site scripting (XSS) filter built
|
||||
# into most web browsers.
|
||||
#
|
||||
# The filter is usually enabled by default, but in some cases it
|
||||
# may be disabled by the user. However, in Internet Explorer for
|
||||
# example, it can be re-enabled just by sending the
|
||||
# `X-XSS-Protection` header with the value of `1`.
|
||||
#
|
||||
# (2) Prevent web browsers from rendering the web page if a potential
|
||||
# reflected (a.k.a non-persistent) XSS attack is detected by the
|
||||
# filter.
|
||||
#
|
||||
# By default, if the filter is enabled and browsers detect a
|
||||
# reflected XSS attack, they will attempt to block the attack
|
||||
# by making the smallest possible modifications to the returned
|
||||
# web page.
|
||||
#
|
||||
# Unfortunately, in some browsers (e.g.: Internet Explorer),
|
||||
# this default behavior may allow the XSS filter to be exploited,
|
||||
# thereby, it's better to inform browsers to prevent the rendering
|
||||
# of the page altogether, instead of attempting to modify it.
|
||||
#
|
||||
# http://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities
|
||||
#
|
||||
# (!) Do not rely on the XSS filter to prevent XSS attacks! Ensure that
|
||||
# you are taking all possible measures to prevent XSS attacks, the
|
||||
# most obvious being: validating and sanitizing your website's inputs.
|
||||
#
|
||||
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx
|
||||
# http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx
|
||||
# https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# # (1) (2)
|
||||
# Header set X-XSS-Protection "1; mode=block"
|
||||
# # `mod_headers` cannot match based on the content-type, however,
|
||||
# # the `X-XSS-Protection` response header should be send only for
|
||||
# # HTML documents and not for the other resources.
|
||||
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
|
||||
# Header unset X-XSS-Protection
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Server software information |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Prevent Apache from sending in the `Server` response header its
|
||||
# exact version number, the description of the generic OS-type or
|
||||
# information about its compiled-in modules.
|
||||
#
|
||||
# (!) The `ServerTokens` directive will only work in the main server
|
||||
# configuration file, so don't try to enable it in the `.htaccess` file!
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/core.html#servertokens
|
||||
|
||||
# ServerTokens Prod
|
||||
|
||||
|
||||
# ######################################################################
|
||||
# # WEB PERFORMANCE #
|
||||
# ######################################################################
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Compression |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
||||
# Force compression for mangled `Accept-Encoding` request headers
|
||||
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Compress all output labeled with one of the following media types.
|
||||
#
|
||||
# (!) For Apache versions below version 2.3.7 you don't need to
|
||||
# enable `mod_filter` and can remove the `<IfModule mod_filter.c>`
|
||||
# and `</IfModule>` lines as `AddOutputFilterByType` is still in
|
||||
# the core directives.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype
|
||||
|
||||
<IfModule mod_filter.c>
|
||||
AddOutputFilterByType DEFLATE "application/atom+xml" \
|
||||
"application/javascript" \
|
||||
"application/json" \
|
||||
"application/ld+json" \
|
||||
"application/manifest+json" \
|
||||
"application/rdf+xml" \
|
||||
"application/rss+xml" \
|
||||
"application/schema+json" \
|
||||
"application/vnd.geo+json" \
|
||||
"application/vnd.ms-fontobject" \
|
||||
"application/x-font-ttf" \
|
||||
"application/x-javascript" \
|
||||
"application/x-web-app-manifest+json" \
|
||||
"application/xhtml+xml" \
|
||||
"application/xml" \
|
||||
"font/opentype" \
|
||||
"image/bmp" \
|
||||
"image/svg+xml" \
|
||||
"image/x-icon" \
|
||||
"text/cache-manifest" \
|
||||
"text/css" \
|
||||
"text/html" \
|
||||
"text/javascript" \
|
||||
"text/plain" \
|
||||
"text/vcard" \
|
||||
"text/vnd.rim.location.xloc" \
|
||||
"text/vtt" \
|
||||
"text/x-component" \
|
||||
"text/xml"
|
||||
</IfModule>
|
||||
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
# Map the following filename extensions to the specified
|
||||
# encoding type in order to make Apache serve the file types
|
||||
# with the appropriate `Content-Encoding` response header
|
||||
# (do note that this will NOT make Apache compress them!).
|
||||
#
|
||||
# If these files types would be served without an appropriate
|
||||
# `Content-Enable` response header, client applications (e.g.:
|
||||
# browsers) wouldn't know that they first need to uncompress
|
||||
# the response, and thus, wouldn't be able to understand the
|
||||
# content.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
|
||||
|
||||
<IfModule mod_mime.c>
|
||||
AddEncoding gzip svgz
|
||||
</IfModule>
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Content transformation |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Prevent intermediate caches or proxies (e.g.: such as the ones
|
||||
# used by mobile network providers) from modifying the website's
|
||||
# content.
|
||||
#
|
||||
# https://tools.ietf.org/html/rfc2616#section-14.9.5
|
||||
#
|
||||
# (!) If you are using `mod_pagespeed`, please note that setting
|
||||
# the `Cache-Control: no-transform` response header will prevent
|
||||
# `PageSpeed` from rewriting `HTML` files, and, if the
|
||||
# `ModPagespeedDisableRewriteOnNoTransform` directive isn't set
|
||||
# to `off`, also from rewriting other resources.
|
||||
#
|
||||
# https://developers.google.com/speed/pagespeed/module/configuration#notransform
|
||||
|
||||
# <IfModule mod_headers.c>
|
||||
# Header merge Cache-Control "no-transform"
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | ETags |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Remove `ETags` as resources are sent with far-future expires headers.
|
||||
#
|
||||
# https://developer.yahoo.com/performance/rules.html#etags
|
||||
# https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
|
||||
# `FileETag None` doesn't work in all cases.
|
||||
<IfModule mod_headers.c>
|
||||
Header unset ETag
|
||||
</IfModule>
|
||||
|
||||
FileETag None
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Expires headers |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Serve resources with far-future expires headers.
|
||||
#
|
||||
# (!) If you don't control versioning with filename-based
|
||||
# cache busting, you should consider lowering the cache times
|
||||
# to something like one week.
|
||||
#
|
||||
# https://httpd.apache.org/docs/current/mod/mod_expires.html
|
||||
|
||||
<IfModule mod_expires.c>
|
||||
|
||||
ExpiresActive on
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
# CSS
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
|
||||
# Data interchange
|
||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
||||
ExpiresByType application/rdf+xml "access plus 1 hour"
|
||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
||||
|
||||
ExpiresByType application/json "access plus 0 seconds"
|
||||
ExpiresByType application/ld+json "access plus 0 seconds"
|
||||
ExpiresByType application/schema+json "access plus 0 seconds"
|
||||
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
|
||||
ExpiresByType application/xml "access plus 0 seconds"
|
||||
ExpiresByType text/xml "access plus 0 seconds"
|
||||
|
||||
# Favicon (cannot be renamed!) and cursor images
|
||||
ExpiresByType image/x-icon "access plus 1 week"
|
||||
|
||||
# HTML
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
|
||||
# JavaScript
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
ExpiresByType application/x-javascript "access plus 1 year"
|
||||
ExpiresByType text/javascript "access plus 1 year"
|
||||
|
||||
# Manifest files
|
||||
ExpiresByType application/manifest+json "access plus 1 year"
|
||||
|
||||
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
|
||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
||||
|
||||
# Media files
|
||||
ExpiresByType audio/ogg "access plus 1 month"
|
||||
ExpiresByType image/bmp "access plus 1 month"
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType video/mp4 "access plus 1 month"
|
||||
ExpiresByType video/ogg "access plus 1 month"
|
||||
ExpiresByType video/webm "access plus 1 month"
|
||||
|
||||
# Web fonts
|
||||
ExpiresByType application/font-woff "access plus 1 month"
|
||||
ExpiresByType application/font-woff2 "access plus 1 month"
|
||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
||||
ExpiresByType font/opentype "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
|
||||
</IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | File concatenation |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Allow concatenation from within specific files.
|
||||
#
|
||||
# e.g.:
|
||||
#
|
||||
# If you have the following lines in a file called, for
|
||||
# example, `main.combined.js`:
|
||||
#
|
||||
# <!--#include file="js/jquery.js" -->
|
||||
# <!--#include file="js/jquery.timer.js" -->
|
||||
#
|
||||
# Apache will replace those lines with the content of the
|
||||
# specified files.
|
||||
|
||||
# <IfModule mod_include.c>
|
||||
# <FilesMatch "\.combined\.js$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES application/javascript \
|
||||
# application/x-javascript \
|
||||
# text/javascript
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# <FilesMatch "\.combined\.css$">
|
||||
# Options +Includes
|
||||
# AddOutputFilterByType INCLUDES text/css
|
||||
# SetOutputFilter INCLUDES
|
||||
# </FilesMatch>
|
||||
# </IfModule>
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# | Filename-based cache busting |
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# If you're not using a build process to manage your filename version
|
||||
# revving, you might want to consider enabling the following directives
|
||||
# to route all requests such as `/style.12345.css` to `/style.css`.
|
||||
#
|
||||
# To understand why this is important and even a better solution than
|
||||
# using something like `*.css?v231`, please see:
|
||||
# http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
|
||||
|
||||
# <IfModule mod_rewrite.c>
|
||||
# RewriteEngine On
|
||||
# RewriteCond %{REQUEST_FILENAME} !-f
|
||||
# RewriteRule ^(.+)\.(\d+)\.(bmp|css|cur|gif|ico|jpe?g|js|png|svgz?|webp)$ $1.$3 [L]
|
||||
# </IfModule>
|
||||
@@ -6,7 +6,11 @@ import Persist from './stores/Persist';
|
||||
|
||||
import Header from './components/Header';
|
||||
import Tooltip from './components/Tooltip';
|
||||
import ModalExport from './components/ModalExport';
|
||||
import ModalHelp from './components/ModalHelp';
|
||||
import ModalImport from './components/ModalImport';
|
||||
import ModalPermalink from './components/ModalPermalink';
|
||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
||||
|
||||
import AboutPage from './pages/AboutPage';
|
||||
import NotFoundPage from './pages/NotFoundPage';
|
||||
@@ -15,6 +19,8 @@ import ComparisonPage from './pages/ComparisonPage';
|
||||
import ShipyardPage from './pages/ShipyardPage';
|
||||
import ErrorDetails from './pages/ErrorDetails';
|
||||
|
||||
const zlib = require('zlib');
|
||||
|
||||
/**
|
||||
* Coriolis App
|
||||
*/
|
||||
@@ -52,6 +58,7 @@ export default class Coriolis extends React.Component {
|
||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._importBuild = this._importBuild.bind(this);
|
||||
|
||||
this.emitter = new EventEmitter();
|
||||
this.state = {
|
||||
@@ -63,13 +70,36 @@ export default class Coriolis extends React.Component {
|
||||
};
|
||||
|
||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||
Router('/import?', (r) => this._importBuild(r));
|
||||
Router('/import/:data', (r) => this._importBuild(r));
|
||||
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/about', (r) => this._setPage(AboutPage, r));
|
||||
Router('*', (r) => this._setPage(null, r));
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a build directly
|
||||
* @param {Object} r The current route
|
||||
*/
|
||||
_importBuild(r) {
|
||||
try {
|
||||
// Need to decode and gunzip the data, then build the ship
|
||||
const data = zlib.gunzipSync(new Buffer(r.params.data, 'base64'));
|
||||
const json = JSON.parse(data);
|
||||
const ship = CompanionApiUtils.shipFromJson(json);
|
||||
r.params.ship = ship.id;
|
||||
r.params.code = ship.toString();
|
||||
this._setPage(OutfittingPage, r);
|
||||
} catch (err) {
|
||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates / Sets the page and route context
|
||||
* @param {[type]} page The page to be shown
|
||||
@@ -131,14 +161,26 @@ export default class Coriolis extends React.Component {
|
||||
this._hideModal();
|
||||
this._closeMenu();
|
||||
break;
|
||||
case 72: // 'h'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalHelp />);
|
||||
}
|
||||
break;
|
||||
case 73: // 'i'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalImport />);
|
||||
}
|
||||
break;
|
||||
case 101010: // 's'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
||||
case 79: // 'o'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + o
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalPermalink url={window.location.href}/>);
|
||||
}
|
||||
break;
|
||||
case 83: // 's'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + s
|
||||
e.preventDefault();
|
||||
this.emitter.emit('command', 'save');
|
||||
}
|
||||
@@ -199,14 +241,19 @@ export default class Coriolis extends React.Component {
|
||||
/**
|
||||
* Show the term tip
|
||||
* @param {string} term Term or Phrase
|
||||
* @param {Object} opts Options - dontCap, orientation (n,e,s,w)
|
||||
* @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
|
||||
* @param {SyntheticEvent} event Event
|
||||
* @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
|
||||
*/
|
||||
_termtip(term, opts, event) {
|
||||
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent
|
||||
_termtip(term, opts, event, e2) {
|
||||
if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
|
||||
event = opts;
|
||||
opts = { cap: true };
|
||||
}
|
||||
if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
|
||||
event = e2;
|
||||
}
|
||||
|
||||
this._tooltip(
|
||||
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
|
||||
event.currentTarget.getBoundingClientRect(),
|
||||
@@ -291,7 +338,7 @@ export default class Coriolis extends React.Component {
|
||||
{ this.state.tooltip }
|
||||
<footer>
|
||||
<div className="right cap">
|
||||
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>;
|
||||
|
||||
@@ -76,7 +76,16 @@ Router.go = function(path, state) {
|
||||
if (isStandAlone()) {
|
||||
Persist.setState(ctx);
|
||||
}
|
||||
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
|
||||
try {
|
||||
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
|
||||
} catch (ex) {
|
||||
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
|
||||
state: ctx.state,
|
||||
title: ctx.title,
|
||||
path: ctx.canonicalPath
|
||||
}));
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
@@ -99,7 +108,16 @@ Router.replace = function(path, state, dispatch) {
|
||||
if (isStandAlone()) {
|
||||
Persist.setState(ctx);
|
||||
}
|
||||
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
|
||||
try {
|
||||
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
|
||||
} catch (ex) {
|
||||
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
|
||||
state: ctx.state,
|
||||
title: ctx.title,
|
||||
path: ctx.canonicalPath
|
||||
}));
|
||||
location.reload();
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
@@ -7,6 +8,75 @@ import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
|
||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||
|
||||
/*
|
||||
* Categorisation of module groups
|
||||
*/
|
||||
const GRPCAT = {
|
||||
'sg': 'shields',
|
||||
'bsg': 'shields',
|
||||
'psg': 'shields',
|
||||
'scb': 'shields',
|
||||
'cc': 'limpet controllers',
|
||||
'fx': 'limpet controllers',
|
||||
'hb': 'limpet controllers',
|
||||
'pc': 'limpet controllers',
|
||||
'pce': 'passenger cabins',
|
||||
'pci': 'passenger cabins',
|
||||
'pcm': 'passenger cabins',
|
||||
'pcq': 'passenger cabins',
|
||||
'fh': 'hangars',
|
||||
'pv': 'hangars',
|
||||
'fs': 'fuel',
|
||||
'ft': 'fuel',
|
||||
'hr': 'structural reinforcement',
|
||||
'mrp': 'structural reinforcement',
|
||||
'bl': 'lasers',
|
||||
'pl': 'lasers',
|
||||
'ul': 'lasers',
|
||||
'ml': 'lasers',
|
||||
'c': 'projectiles',
|
||||
'mc': 'projectiles',
|
||||
'fc': 'projectiles',
|
||||
'pa': 'projectiles',
|
||||
'rg': 'projectiles',
|
||||
'mr': 'ordnance',
|
||||
'tp': 'ordnance',
|
||||
'nl': 'ordnance',
|
||||
'sc': 'scanners',
|
||||
'ss': 'scanners',
|
||||
// Utilities
|
||||
'cs': 'scanners',
|
||||
'kw': 'scanners',
|
||||
'ws': 'scanners',
|
||||
'ch': 'defence',
|
||||
'po': 'defence',
|
||||
'ec': 'defence',
|
||||
};
|
||||
// Order here is the order in which items will be shown in the modules menu
|
||||
const CATEGORIES = {
|
||||
// Internals
|
||||
'am': ['am'],
|
||||
'cr': ['cr'],
|
||||
'fi': ['fi'],
|
||||
'fuel': ['ft', 'fs'],
|
||||
'hangars': ['fh', 'pv'],
|
||||
'limpet controllers': ['cc', 'fx', 'hb', 'pc'],
|
||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
||||
'rf': ['rf'],
|
||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
||||
'structural reinforcement': ['hr', 'mrp'],
|
||||
'dc': ['dc'],
|
||||
// Hardpoints
|
||||
'lasers': ['pl', 'ul', 'bl', 'ml'],
|
||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
||||
'ordnance': ['mr', 'tp', 'nl'],
|
||||
// Utilities
|
||||
'sb': ['sb'],
|
||||
'hs': ['hs'],
|
||||
'defence': ['ch', 'po', 'ec'],
|
||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
||||
};
|
||||
|
||||
/**
|
||||
* Available modules menu
|
||||
*/
|
||||
@@ -63,15 +133,55 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
} else {
|
||||
list = [];
|
||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
||||
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
|
||||
for (let g in modules) {
|
||||
if (m && g == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
|
||||
} else {
|
||||
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
|
||||
}
|
||||
if (m) {
|
||||
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
|
||||
}
|
||||
|
||||
list.push(buildGroup(g, modules[g]));
|
||||
// Need to regroup the modules by our own categorisation
|
||||
let catmodules = {};
|
||||
// Pre-create to preserve ordering
|
||||
for (let cat in CATEGORIES) {
|
||||
catmodules[cat] = [];
|
||||
}
|
||||
for (let g in modules) {
|
||||
const moduleCategory = GRPCAT[g] || g;
|
||||
const existing = catmodules[moduleCategory] || [];
|
||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
||||
}
|
||||
|
||||
for (let category in catmodules) {
|
||||
let categoryHeader = false;
|
||||
// Order through CATEGORIES if present
|
||||
const categories = CATEGORIES[category] || [category];
|
||||
if (categories && categories.length) {
|
||||
for (let n in categories) {
|
||||
const grp = categories[n];
|
||||
// We now have the group and the category. We might not have any modules, though...
|
||||
if (modules[grp]) {
|
||||
// Decide if we need a category header as well as a group header
|
||||
if (categories.length === 1) {
|
||||
// Show category header instead of group header
|
||||
if (m && grp == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
} else {
|
||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
}
|
||||
} else {
|
||||
// Show category header as well as group header
|
||||
if (!categoryHeader) {
|
||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
categoryHeader = true;
|
||||
}
|
||||
if (m && grp == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
||||
} else {
|
||||
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
||||
}
|
||||
}
|
||||
list.push(buildGroup(grp, modules[grp]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +203,26 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
let prevClass = null, prevRating = null;
|
||||
let elems = [];
|
||||
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
let m = modules[i];
|
||||
const sortedModules = modules.sort(this._moduleOrder);
|
||||
|
||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
|
||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
||||
|
||||
let itemsOnThisRow = 0;
|
||||
|
||||
for (let i = 0; i < sortedModules.length; i++) {
|
||||
let m = sortedModules[i];
|
||||
let mount = null;
|
||||
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
|
||||
let active = mountedModule && mountedModule === m;
|
||||
let disabled = false;
|
||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
||||
// Shield generators care about maximum hull mass
|
||||
disabled = mass > m.maxmass;
|
||||
} else if (m.maxmass) {
|
||||
// Thrusters care about total mass
|
||||
disabled = mass + m.mass > m.maxmass;
|
||||
}
|
||||
let active = mountedModule && mountedModule.id === m.id;
|
||||
let classes = cn(m.name ? 'lc' : 'c', {
|
||||
warning: !disabled && warningFunc && warningFunc(m),
|
||||
active,
|
||||
@@ -126,8 +251,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
||||
}
|
||||
|
||||
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
|
||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
||||
elems.push(<br key={'b' + m.grp + i} />);
|
||||
itemsOnThisRow = 0;
|
||||
}
|
||||
|
||||
elems.push(
|
||||
@@ -136,6 +262,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
||||
</li>
|
||||
);
|
||||
itemsOnThisRow++;
|
||||
prevClass = m.class;
|
||||
prevRating = m.rating;
|
||||
}
|
||||
@@ -201,6 +328,46 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
this.context.tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Order two modules suitably for display in module selection
|
||||
* @param {Object} a the first module
|
||||
* @param {Object} b the second module
|
||||
* @return {int} -1 if the first module should go first, 1 if the second module should go first
|
||||
*/
|
||||
_moduleOrder(a, b) {
|
||||
// Named modules go last
|
||||
if (!a.name && b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name && !b.name) {
|
||||
return 1;
|
||||
}
|
||||
// Class ordered from highest (8) to lowest (1)
|
||||
if (a.class < b.class) {
|
||||
return 1;
|
||||
}
|
||||
if (a.class > b.class) {
|
||||
return -1;
|
||||
}
|
||||
// Mount type, if applicable
|
||||
if (a.mount && b.mount && a.mount !== b.mount) {
|
||||
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Rating ordered from highest (A) to lowest (E)
|
||||
if (a.rating < b.rating) {
|
||||
return -1;
|
||||
}
|
||||
if (a.rating > b.rating) {
|
||||
return 1;
|
||||
}
|
||||
// Do not attempt to order by name at this point, as that mucks up the order of armour
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to mounted (if it exists) module group on mount
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 };
|
||||
@@ -44,7 +44,7 @@ export default class BarChart extends TranslatedComponent {
|
||||
unit: ''
|
||||
};
|
||||
|
||||
static PropTypes = {
|
||||
static propTypes = {
|
||||
colors: React.PropTypes.array,
|
||||
data: React.PropTypes.array.isRequired,
|
||||
desc: React.PropTypes.bool,
|
||||
@@ -68,13 +68,13 @@ export default class BarChart extends TranslatedComponent {
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._hideTip = this._hideTip.bind(this);
|
||||
|
||||
let scale = d3.scale.linear();
|
||||
let y0 = d3.scale.ordinal();
|
||||
let y1 = d3.scale.ordinal();
|
||||
let scale = d3.scaleLinear();
|
||||
let y0 = d3.scaleBand();
|
||||
let y1 = d3.scaleBand();
|
||||
|
||||
this.xAxis = d3.svg.axis().scale(scale).ticks(5).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.s2);
|
||||
this.yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left');
|
||||
this.state = { scale, y0, y1, color: d3.scale.ordinal().range(props.colors) };
|
||||
this.xAxis = d3.axisBottom(scale).ticks(5).tickSizeOuter(0).tickFormat(context.language.formats.s2);
|
||||
this.yAxis = d3.axisLeft(y0).tickSizeOuter(0);
|
||||
this.state = { scale, y0, y1, color: d3.scaleOrdinal().range(props.colors) };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,8 +131,8 @@ export default class BarChart extends TranslatedComponent {
|
||||
let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0);
|
||||
|
||||
this.state.scale.range([0, innerWidth]).domain([0, max]);
|
||||
this.state.y0.domain(data.map(bName)).rangeRoundBands([0, innerHeight], 0.3);
|
||||
this.state.y1.domain(properties).rangeRoundBands([0, this.state.y0.rangeBand()]);
|
||||
this.state.y0.domain(data.map(bName)).range([0, innerHeight], 0.3).padding(0.4);
|
||||
this.state.y1.domain(properties).range([0, this.state.y0.bandwidth()]).padding(0.1);
|
||||
|
||||
this.setState({
|
||||
barHeight,
|
||||
@@ -192,7 +192,7 @@ export default class BarChart extends TranslatedComponent {
|
||||
x={0}
|
||||
y={y1(p)}
|
||||
width={scale(build[p])}
|
||||
height={y1.rangeBand()}
|
||||
height={y1.bandwidth()}
|
||||
fill={color(p)}
|
||||
onMouseOver={this._showTip.bind(this, build, p, propIndex)}
|
||||
onMouseOut={this._hideTip}
|
||||
|
||||
89
src/app/components/Boost.jsx
Normal file
89
src/app/components/Boost.jsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
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
|
||||
* Requires an onChange() function of the form onChange(boost) which is triggered whenever the boost changes.
|
||||
*/
|
||||
export default class Boost extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
boost: React.PropTypes.bool.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
const { ship, boost } = props;
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._toggleBoost = this._toggleBoost.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners after mounting
|
||||
*/
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners before unmounting
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Key Down
|
||||
* @param {Event} e Keyboard Event
|
||||
*/
|
||||
_keyDown(e) {
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
|
||||
switch (e.keyCode) {
|
||||
case 66: // b == boost
|
||||
if (this.props.ship.canBoost()) {
|
||||
e.preventDefault();
|
||||
this._toggleBoost();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the boost feature
|
||||
*/
|
||||
_toggleBoost() {
|
||||
this.props.onChange(!this.props.boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render boost
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { ship, boost } = this.props;
|
||||
|
||||
// TODO disable if ship cannot boost
|
||||
return (
|
||||
<span id='boost'>
|
||||
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
75
src/app/components/Cargo.jsx
Normal file
75
src/app/components/Cargo.jsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
* Cargo slider
|
||||
* Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change
|
||||
*/
|
||||
export default class Cargo extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
cargo: React.PropTypes.number.isRequired,
|
||||
cargoCapacity: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._cargoChange = this._cargoChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cargo level
|
||||
* @param {number} cargoLevel percentage level from 0 to 1
|
||||
*/
|
||||
_cargoChange(cargoLevel) {
|
||||
const { cargo, cargoCapacity } = this.props;
|
||||
if (cargoCapacity > 0) {
|
||||
// We round the cargo to whole number of tonnes
|
||||
const newCargo = Math.round(cargoLevel * cargoCapacity);
|
||||
if (newCargo != cargo) {
|
||||
this.props.onChange(newCargo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render cargo slider
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { cargo, cargoCapacity } = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._cargoChange}
|
||||
axisUnit={translate('T')}
|
||||
percent={cargo / cargoCapacity}
|
||||
max={cargoCapacity}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Link from './Link';
|
||||
import cn from 'classnames';
|
||||
import { SizeMap } from '../shipyard/Constants';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
|
||||
/**
|
||||
@@ -71,7 +71,7 @@ export default class ComparisonTable extends TranslatedComponent {
|
||||
* @return {React.Component} Table row
|
||||
*/
|
||||
_buildRow(build, facets, formats, units) {
|
||||
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`;
|
||||
let url = outfitURL(build.id, build.toString(), build.buildName);
|
||||
let cells = [
|
||||
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
|
||||
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>
|
||||
|
||||
@@ -6,13 +6,14 @@ import Ship from '../shipyard/Ship';
|
||||
import { Insurance } from '../shipyard/Constants';
|
||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { ShoppingIcon } from '../components/SvgIcons';
|
||||
|
||||
/**
|
||||
* Cost Section
|
||||
*/
|
||||
export default class CostSection extends TranslatedComponent {
|
||||
|
||||
static PropTypes = {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired,
|
||||
buildName: React.PropTypes.string
|
||||
@@ -31,6 +32,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
||||
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
||||
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
||||
this._eddbShoppingList = this._eddbShoppingList.bind(this);
|
||||
|
||||
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
||||
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
||||
@@ -325,12 +327,27 @@ export default class CostSection extends TranslatedComponent {
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||
*/
|
||||
_eddbShoppingList() {
|
||||
const { retrofitCosts } = this.state;
|
||||
const { ship } = this.props;
|
||||
|
||||
// Provide unique list of non-PP module EDDB IDs to buy
|
||||
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||
|
||||
// Open up the relevant URL
|
||||
window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the retofit tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_retrofitTab() {
|
||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
||||
const { termtip, tooltip } = this.context;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let int = formats.int;
|
||||
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
||||
@@ -370,7 +387,8 @@ export default class CostSection extends TranslatedComponent {
|
||||
<tbody>
|
||||
{rows}
|
||||
<tr className='ri'>
|
||||
<td colSpan='4' className='lbl' >{translate('cost')}</td>
|
||||
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
||||
<td colSpan='3' className='lbl' >{translate('cost')}</td>
|
||||
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||
{int(retrofitTotal)}{units.CR}
|
||||
</td>
|
||||
@@ -379,7 +397,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
|
||||
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName} onChange={this._onBaseRetrofitChange}>
|
||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
||||
{options}
|
||||
</select>
|
||||
</td>
|
||||
@@ -403,6 +421,8 @@ export default class CostSection extends TranslatedComponent {
|
||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
||||
item = {
|
||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
||||
buyId: ship.bulkheads.m.eddbID,
|
||||
buyPp: ship.bulkheads.m.pp,
|
||||
buyName: ship.bulkheads.m.name,
|
||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
||||
sellName: retrofitShip.bulkheads.m.name,
|
||||
@@ -419,9 +439,13 @@ export default class CostSection extends TranslatedComponent {
|
||||
let retroSlotGroup = retrofitShip[g];
|
||||
let slotGroup = ship[g];
|
||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
||||
if (slotGroup[i].m != retroSlotGroup[i].m) {
|
||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
||||
if (modId != retroModId) {
|
||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
||||
if (slotGroup[i].m) {
|
||||
item.buyId = slotGroup[i].m.eddbID,
|
||||
item.buyPp = slotGroup[i].m.pp,
|
||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
||||
item.netCost = slotGroup[i].discountedCost;
|
||||
@@ -505,19 +529,19 @@ export default class CostSection extends TranslatedComponent {
|
||||
scoop = true;
|
||||
break;
|
||||
case 'scb':
|
||||
q = slotGroup[i].m.cells;
|
||||
q = slotGroup[i].m.getAmmo() + 1;
|
||||
break;
|
||||
case 'am':
|
||||
q = slotGroup[i].m.ammo;
|
||||
q = slotGroup[i].m.getAmmo();
|
||||
break;
|
||||
case 'pv':
|
||||
srvs += slotGroup[i].m.vehicles;
|
||||
srvs += slotGroup[i].m.getBays();
|
||||
break;
|
||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
||||
limpets = ship.cargoCapacity;
|
||||
break;
|
||||
default:
|
||||
q = slotGroup[i].m.clip + slotGroup[i].m.ammo;
|
||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
||||
}
|
||||
// Calculate ammo costs only if a cost is specified
|
||||
if (slotGroup[i].m.ammocost > 0) {
|
||||
@@ -530,6 +554,17 @@ export default class CostSection extends TranslatedComponent {
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
// Add fighters
|
||||
if (slotGroup[i].m.grp === 'fh') {
|
||||
item = {
|
||||
m: slotGroup[i].m,
|
||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
||||
cost: slotGroup[i].m.fightercost,
|
||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,12 +585,13 @@ export default class CostSection extends TranslatedComponent {
|
||||
item = {
|
||||
m: { name: 'SRVs', class: '', rating: '' },
|
||||
max: srvs,
|
||||
cost: 6005,
|
||||
total: srvs * 6005
|
||||
cost: 1030,
|
||||
total: srvs * 1030
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
|
||||
// Calculate refuel costs if no scoop present
|
||||
if (!scoop) {
|
||||
item = {
|
||||
@@ -606,6 +642,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
}
|
||||
|
||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
||||
this._updateAmmoCosts(nextProps.ship);
|
||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
||||
this._sortCost(nextProps.ship);
|
||||
|
||||
261
src/app/components/Defence.jsx
Normal file
261
src/app/components/Defence.jsx
Normal file
@@ -0,0 +1,261 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
|
||||
/**
|
||||
* Defence information
|
||||
* Shield information consists of four panels:
|
||||
* - textual information (time to lose shields etc.)
|
||||
* - breakdown of shield sources (pie chart)
|
||||
* - comparison of shield resistances (bar chart)
|
||||
* - effective shield (bar chart)
|
||||
*/
|
||||
export default class Defence extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
opponent: React.PropTypes.object.isRequired,
|
||||
engagementrange: React.PropTypes.number.isRequired,
|
||||
sys: React.PropTypes.number.isRequired,
|
||||
opponentWep: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
|
||||
this.state = { shield, armour, shielddamage, armourdamage };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our properties change
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
|
||||
this.setState({ shield, armour, shielddamage, armourdamage });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render defence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, 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 shieldSourcesData = [];
|
||||
const effectiveShieldData = [];
|
||||
const shieldDamageTakenData = [];
|
||||
const shieldSourcesTt = [];
|
||||
const shieldDamageTakenAbsoluteTt = [];
|
||||
const shieldDamageTakenExplosiveTt = [];
|
||||
const shieldDamageTakenKineticTt = [];
|
||||
const shieldDamageTakenThermalTt = [];
|
||||
const effectiveShieldAbsoluteTt = [];
|
||||
const effectiveShieldExplosiveTt = [];
|
||||
const effectiveShieldKineticTt = [];
|
||||
const effectiveShieldThermalTt = [];
|
||||
let maxEffectiveShield = 0;
|
||||
if (shield.total) {
|
||||
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
||||
|
||||
if (shield.generator > 0) {
|
||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
if (shield.boosters > 0) {
|
||||
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
}
|
||||
|
||||
if (shield.cells > 0) {
|
||||
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
}
|
||||
|
||||
// Add effective shield from resistances
|
||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
||||
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
|
||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
||||
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
|
||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
||||
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
|
||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
||||
|
||||
// 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>);
|
||||
}
|
||||
}
|
||||
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
||||
|
||||
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
||||
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
||||
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
||||
|
||||
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
|
||||
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
||||
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
||||
|
||||
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
|
||||
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
||||
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
||||
|
||||
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
|
||||
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
|
||||
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
|
||||
const effectiveThermalShield = shield.total / shield.thermal.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
|
||||
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
|
||||
|
||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
||||
}
|
||||
|
||||
const armourSourcesData = [];
|
||||
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
||||
|
||||
const armourSourcesTt = [];
|
||||
const effectiveArmourAbsoluteTt = [];
|
||||
const effectiveArmourExplosiveTt = [];
|
||||
const effectiveArmourKineticTt = [];
|
||||
const effectiveArmourThermalTt = [];
|
||||
if (armour.bulkheads > 0) {
|
||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
if (armour.reinforcement > 0) {
|
||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const rawArmour = armour.bulkheads + armour.reinforcement;
|
||||
|
||||
const armourDamageTakenTt = [];
|
||||
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
||||
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
||||
|
||||
const armourDamageTakenExplosiveTt = [];
|
||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
||||
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenKineticTt = [];
|
||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
||||
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenThermalTt = [];
|
||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
||||
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const effectiveArmourData = [];
|
||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
|
||||
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
|
||||
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
|
||||
|
||||
const armourDamageTakenData = [];
|
||||
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
|
||||
|
||||
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>
|
||||
</span> : null }
|
||||
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('armour metrics')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour sources')}</h2>
|
||||
<PieChart data={armourSourcesData} />
|
||||
</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={armourDamageTakenData} yMax={100} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective armour')}</h2>
|
||||
<VerticalBarChart data={effectiveArmourData} />
|
||||
</div>
|
||||
</span>);
|
||||
}
|
||||
}
|
||||
101
src/app/components/EngagementRange.jsx
Normal file
101
src/app/components/EngagementRange.jsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
* Engagement range slider
|
||||
* Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change
|
||||
*/
|
||||
export default class EngagementRange extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
engagementRange: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const { ship } = props;
|
||||
|
||||
const maxRange = this._calcMaxRange(ship);
|
||||
|
||||
this.state = {
|
||||
maxRange
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range of a ship's weapons
|
||||
* @param {Object} ship The ship
|
||||
* @returns {int} The maximum range, in metres
|
||||
*/
|
||||
_calcMaxRange(ship) {
|
||||
let maxRange = 1000;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const thisRange = ship.hardpoints[i].m.getRange();
|
||||
if (thisRange > maxRange) {
|
||||
maxRange = thisRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update range
|
||||
* @param {number} rangeLevel percentage level from 0 to 1
|
||||
*/
|
||||
_rangeChange(rangeLevel) {
|
||||
const { maxRange } = this.state;
|
||||
|
||||
// We round the range to an integer value
|
||||
const range = Math.round(rangeLevel * maxRange);
|
||||
|
||||
if (range !== this.props.engagementRange) {
|
||||
this.props.onChange(range);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render range slider
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { engagementRange } = this.props;
|
||||
const { maxRange } = this.state;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h3>{translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}</h3>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._rangeChange.bind(this)}
|
||||
axisUnit={translate('m')}
|
||||
percent={engagementRange / maxRange}
|
||||
max={maxRange}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
105
src/app/components/EngineProfile.jsx
Normal file
105
src/app/components/EngineProfile.jsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Engine profile for a given ship
|
||||
*/
|
||||
export default class EngineProfile extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
cargo: React.PropTypes.number.isRequired,
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
eng: React.PropTypes.number.isRequired,
|
||||
boost: React.PropTypes.bool.isRequired,
|
||||
marker: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} eng The number of pips to ENG
|
||||
* @param {Object} boost If boost is enabled
|
||||
* @param {Object} mass The mass at which to calculate the top speed
|
||||
* @return {number} The maximum speed
|
||||
*/
|
||||
calcMaxSpeed(ship, eng, boost, mass) {
|
||||
// Obtain the top speed
|
||||
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render engine profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, eng, fuel, boost } = this.props;
|
||||
|
||||
// Calculate bounds for our line chart
|
||||
const thrusters = ship.standard[1].m;
|
||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
||||
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
||||
|
||||
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||
return (
|
||||
<LineChart
|
||||
xMin={minMass}
|
||||
xMax={maxMass}
|
||||
yMin={minSpeed}
|
||||
yMax={maxSpeed}
|
||||
xMark={mark}
|
||||
xLabel={translate('mass')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('maximum speed')}
|
||||
yUnit={translate('m/s')}
|
||||
func={this.state.calcMaxSpeedFunc}
|
||||
points={1000}
|
||||
code={code}
|
||||
aspect={0.7}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
103
src/app/components/FSDProfile.jsx
Normal file
103
src/app/components/FSDProfile.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
/**
|
||||
* FSD profile for a given ship
|
||||
*/
|
||||
export default class FSDProfile extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
cargo: React.PropTypes.number.isRequired,
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
marker: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range for this ship across its applicable mass
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} fuel The fuel on the ship
|
||||
* @param {Object} mass The mass at which to calculate the maximum range
|
||||
* @return {number} The maximum range
|
||||
*/
|
||||
_calcMaxRange(ship, fuel, mass) {
|
||||
// Obtain the maximum range
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render FSD profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
|
||||
|
||||
// Calculate bounds for our line chart - use thruster info for X
|
||||
const thrusters = ship.standard[1].m;
|
||||
const fsd = ship.standard[2].m;
|
||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minRange = 0;
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
const code = ship.name + ship.toString() + '.' + fuel;
|
||||
|
||||
return (
|
||||
<LineChart
|
||||
xMin={minMass}
|
||||
xMax={maxMass}
|
||||
yMin={minRange}
|
||||
yMax={maxRange}
|
||||
xMark={mark}
|
||||
xLabel={translate('mass')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('maximum range')}
|
||||
yUnit={translate('LY')}
|
||||
func={this.state.calcMaxRangeFunc}
|
||||
points={200}
|
||||
code={code}
|
||||
aspect={0.7}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
74
src/app/components/Fuel.jsx
Normal file
74
src/app/components/Fuel.jsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
* Fuel slider
|
||||
* Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change
|
||||
*/
|
||||
export default class Fuel extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
fuelCapacity: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._fuelChange = this._fuelChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update fuel level
|
||||
* @param {number} fuelLevel percentage level from 0 to 1
|
||||
*/
|
||||
_fuelChange(fuelLevel) {
|
||||
const { fuel, fuelCapacity } = this.props;
|
||||
|
||||
const newFuel = fuelLevel * fuelCapacity;
|
||||
// Only send an update if the fuel has changed significantly
|
||||
if (Math.round(fuel * 10) != Math.round(newFuel * 10)) {
|
||||
this.props.onChange(Math.round(newFuel * 10) / 10);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render fuel slider
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { fuel, fuelCapacity } = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h3>{translate('fuel carried')}: {formats.f1(fuel)}{units.T}</h3>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._fuelChange}
|
||||
axisUnit={translate('T')}
|
||||
percent={fuel / fuelCapacity}
|
||||
max={fuelCapacity}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import Persist from '../stores/Persist';
|
||||
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
|
||||
/**
|
||||
* Hardpoint / Utility Slot
|
||||
@@ -33,24 +39,62 @@ export default class HardpointSlot extends Slot {
|
||||
*/
|
||||
_getSlotDetails(m, translate, formats, u) {
|
||||
if (m) {
|
||||
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`;
|
||||
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
let { drag, drop } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.modules[m.grp].modifications || [];
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
modTT += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass}{u.T}</div>
|
||||
<div className={'l'}>
|
||||
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
|
||||
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
|
||||
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
|
||||
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
|
||||
</div>
|
||||
|
||||
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null }
|
||||
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
|
||||
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null }
|
||||
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
|
||||
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
|
||||
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
|
||||
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
|
||||
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
|
||||
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</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.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
|
||||
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
|
||||
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
|
||||
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
|
||||
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
|
||||
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
|
||||
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
|
||||
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
|
||||
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
|
||||
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
/**
|
||||
* Hardpoint slot section
|
||||
*/
|
||||
export default class HardpointsSlotSection extends SlotSection {
|
||||
export default class HardpointSlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -69,6 +69,7 @@ export default class HardpointsSlotSection extends SlotSection {
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this, h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
@@ -126,6 +127,20 @@ export default class HardpointsSlotSection extends SlotSection {
|
||||
<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>
|
||||
</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>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('pa')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('nl')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Insurance } from '../shipyard/Constants';
|
||||
import Link from './Link';
|
||||
import ActiveLink from './ActiveLink';
|
||||
import cn from 'classnames';
|
||||
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
|
||||
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Persist from '../stores/Persist';
|
||||
import { toDetailedExport } from '../shipyard/Serializer';
|
||||
import ModalDeleteAll from './ModalDeleteAll';
|
||||
import ModalExport from './ModalExport';
|
||||
import ModalHelp from './ModalHelp';
|
||||
import ModalImport from './ModalImport';
|
||||
import Slider from './Slider';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
@@ -27,7 +28,7 @@ function normalizePercent(val) {
|
||||
if (val === '' || isNaN(val)) {
|
||||
return 0;
|
||||
}
|
||||
val = Math.round(val * 100) / 100;
|
||||
val = Math.round(val * 1000) / 1000;
|
||||
return val >= 100 ? 100 : val;
|
||||
}
|
||||
|
||||
@@ -53,11 +54,11 @@ function selectAll(e) {
|
||||
*/
|
||||
export default class Header extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.shipOrder = Object.keys(Ships).sort();
|
||||
@@ -74,6 +75,7 @@ export default class Header extends TranslatedComponent {
|
||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||
this._openComp = this._openMenu.bind(this, 'comp');
|
||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||
this._showHelp = this._showHelp.bind(this);
|
||||
this.languageOptions = [];
|
||||
this.insuranceOptions = [];
|
||||
this.state = {
|
||||
@@ -203,6 +205,13 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.showTooltips(!Persist.showTooltips());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle module resistances setting
|
||||
*/
|
||||
_toggleModuleResistances() {
|
||||
Persist.showModuleResistances(!Persist.showModuleResistances());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show delete all modal
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -241,6 +250,17 @@ export default class Header extends TranslatedComponent {
|
||||
/>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help modal
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showHelp(e) {
|
||||
let translate = this.context.language.translate;
|
||||
e.preventDefault();
|
||||
|
||||
this.context.showModal(<ModalHelp title={translate('help')} />);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show import modal
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -336,7 +356,7 @@ export default class Header extends TranslatedComponent {
|
||||
let comps = Object.keys(Persist.getComparisons()).sort();
|
||||
|
||||
for (let name of comps) {
|
||||
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className='block name'>{name}</ActiveLink>);
|
||||
comparisons.push(<ActiveLink key={name} href={'/compare/' + encodeURIComponent(name)} className='block name'>{name}</ActiveLink>);
|
||||
}
|
||||
} else {
|
||||
comparisons = <span className='cap'>{translate('none created')}</span>;
|
||||
@@ -359,6 +379,7 @@ export default class Header extends TranslatedComponent {
|
||||
_getSettingsMenu() {
|
||||
let translate = this.context.language.translate;
|
||||
let tips = Persist.showTooltips();
|
||||
let moduleResistances = Persist.showModuleResistances();
|
||||
|
||||
return (
|
||||
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
|
||||
@@ -376,6 +397,10 @@ export default class Header extends TranslatedComponent {
|
||||
<td>{translate('tooltips')}</td>
|
||||
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr className='cap ptr' onClick={this._toggleModuleResistances} >
|
||||
<td>{translate('module resistances')}</td>
|
||||
<td className={cn('ri', { disabled: !moduleResistances, 'primary-disabled': moduleResistances })}>{(moduleResistances ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{translate('insurance')}</td>
|
||||
<td className='ri'>
|
||||
@@ -438,6 +463,7 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.addListener('deletedAll', update);
|
||||
Persist.addListener('builds', update);
|
||||
Persist.addListener('tooltips', update);
|
||||
Persist.addListener('moduleresistances', update);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,8 +534,14 @@ export default class Header extends TranslatedComponent {
|
||||
</div>
|
||||
{openedMenu == 'settings' ? this._getSettingsMenu() : null}
|
||||
</div>
|
||||
|
||||
<div className='r menu'>
|
||||
<div className={cn('menu-header')} onClick={this._showHelp}>
|
||||
<Help className='xl warning'/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import { Infinite } from './SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Internal Slot
|
||||
@@ -18,30 +22,62 @@ export default class InternalSlot extends Slot {
|
||||
_getSlotDetails(m, translate, formats, u) {
|
||||
if (m) {
|
||||
let classRating = m.class + m.rating;
|
||||
let { drag, drop } = this.props;
|
||||
let { drag, drop, ship } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.modules[m.grp].modifications || [];
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
|
||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
|
||||
{ m.maxmass ? <div className={'l'}>{translate('max mass')}: {m.maxmass}{u.T}</div> : null }
|
||||
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
|
||||
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
|
||||
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
|
||||
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
|
||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
|
||||
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
|
||||
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u> {translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
|
||||
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
||||
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
|
||||
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
|
||||
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
|
||||
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
||||
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{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.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{translate('armour')}</u></div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
||||
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
|
||||
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
|
||||
@@ -4,6 +4,7 @@ import SlotSection from './SlotSection';
|
||||
import InternalSlot from './InternalSlot';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
|
||||
/**
|
||||
* Internal slot section
|
||||
@@ -16,12 +17,18 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'internal', 'internal compartments');
|
||||
super(props, context, 'internal', 'optional internal');
|
||||
|
||||
this._empty = this._empty.bind(this);
|
||||
this._fillWithCargo = this._fillWithCargo.bind(this);
|
||||
this._fillWithCells = this._fillWithCells.bind(this);
|
||||
this._fillWithArmor = this._fillWithArmor.bind(this);
|
||||
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
|
||||
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
|
||||
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
|
||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
|
||||
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +48,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'cr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
});
|
||||
@@ -49,6 +56,86 @@ export default class InternalSlotSection extends SlotSection {
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with fuel tanks
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFuelTanks(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'ft')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with luxury passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithLuxuryCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcq')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with first class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFirstClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcm')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with business class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithBusinessClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pci')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with economy class passenger cabins
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithEconomyClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pce')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with Shield Cell Banks
|
||||
* @param {SyntheticEvent} event Event
|
||||
@@ -58,7 +145,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let ship = this.props.ship;
|
||||
let chargeCap = 0; // Capacity of single activation
|
||||
ship.internal.forEach(function(slot) {
|
||||
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case
|
||||
if ((clobber && !(slot.m && ModuleUtils.isShieldGenerator(slot.m.grp)) || !slot.m) && canMount(ship, slot, 'scb')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
|
||||
chargeCap += slot.m.recharge;
|
||||
@@ -76,7 +163,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if (clobber || !slot.m) {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'hr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
||||
}
|
||||
});
|
||||
@@ -84,6 +171,22 @@ export default class InternalSlotSection extends SlotSection {
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all slots with Module Reinforcement Packages
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithModuleReinforcementPackages(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'mrp')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all on section header right click
|
||||
*/
|
||||
@@ -99,7 +202,7 @@ export default class InternalSlotSection extends SlotSection {
|
||||
let slots = [];
|
||||
let { currentMenu, ship } = this.props;
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let { internal, fuelCapacity, ladenMass } = ship;
|
||||
let { internal, fuelCapacity } = ship;
|
||||
let availableModules = ship.getAvailableModules();
|
||||
|
||||
for (let i = 0, l = internal.length; i < l; i++) {
|
||||
@@ -108,11 +211,13 @@ export default class InternalSlotSection extends SlotSection {
|
||||
slots.push(<InternalSlot
|
||||
key={i}
|
||||
maxClass={s.maxClass}
|
||||
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)}
|
||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
||||
onOpen={this._openMenu.bind(this,s)}
|
||||
onChange={this.props.onChange}
|
||||
onSelect={this._selectModule.bind(this, s)}
|
||||
selected={currentMenu == s}
|
||||
enabled={s.enabled}
|
||||
eligible={s.eligible}
|
||||
m={s.m}
|
||||
drag={this._drag.bind(this, s)}
|
||||
dragOver={this._dragOverSlot.bind(this, s)}
|
||||
@@ -129,15 +234,22 @@ export default class InternalSlotSection extends SlotSection {
|
||||
/**
|
||||
* Generate the section drop-down menu
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Function} ship The ship
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate) {
|
||||
_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='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
|
||||
123
src/app/components/JumpRange.jsx
Normal file
123
src/app/components/JumpRange.jsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
* Jump range for a given ship
|
||||
*/
|
||||
export default class JumpRange extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
fuelLevel: 1,
|
||||
calcJumpRangeFunc: this._calcJumpRange.bind(this, ship)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.code != this.props.code) {
|
||||
this.setState({ fuelLevel: 1,
|
||||
calcJumpRangeFunc: this._calcJumpRange.bind(this, nextProps.ship) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the jump range this ship at a given cargo
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} cargo The cargo
|
||||
* @return {number} The jump range
|
||||
*/
|
||||
_calcJumpRange(ship, cargo) {
|
||||
// Obtain the FSD for this ship
|
||||
const fsd = ship.standard[2].m;
|
||||
|
||||
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
||||
|
||||
// Obtain the jump range
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update fuel level
|
||||
* @param {number} fuelLevel Fuel level 0 - 1
|
||||
*/
|
||||
_fuelChange(fuelLevel) {
|
||||
this.setState({
|
||||
fuelLevel,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render engine profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship } = this.props;
|
||||
const { fuelLevel } = this.state;
|
||||
|
||||
const code = ship.toString() + '.' + ship.getModificationsString() + '.' + fuelLevel;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('jump range')}</h1>
|
||||
<LineChart
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenRange}
|
||||
xLabel={translate('cargo')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('jump range')}
|
||||
yUnit={translate('LY')}
|
||||
func={this.state.calcJumpRangeFunc}
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
<h3>{translate('fuel carried')}: {formats.f2(fuelLevel * ship.fuelCapacity)}{units.T}</h3>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._fuelChange.bind(this)}
|
||||
axisUnit={translate('T')}
|
||||
percent={fuelLevel}
|
||||
max={ship.fuelCapacity}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const RENDER_POINTS = 20; // Only render 20 points on the graph
|
||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
|
||||
/**
|
||||
@@ -11,24 +11,30 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
export default class LineChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
code: '',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
colors: ['#ff8c0d']
|
||||
points: 20,
|
||||
colors: ['#ff8c0d'],
|
||||
aspect: 0.5
|
||||
};
|
||||
|
||||
static PropTypes = {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
static propTypes = {
|
||||
func: React.PropTypes.func.isRequired,
|
||||
xLabel: React.PropTypes.string.isRequired,
|
||||
xMin: React.PropTypes.number,
|
||||
xMax: React.PropTypes.number.isRequired,
|
||||
xUnit: React.PropTypes.string.isRequired,
|
||||
xMark: React.PropTypes.number,
|
||||
yLabel: React.PropTypes.string.isRequired,
|
||||
yMin: React.PropTypes.number,
|
||||
yMax: React.PropTypes.number.isRequired,
|
||||
yUnit: React.PropTypes.string.isRequired,
|
||||
yUnit: React.PropTypes.string,
|
||||
series: React.PropTypes.array,
|
||||
colors: React.PropTypes.array,
|
||||
points: React.PropTypes.number,
|
||||
aspect: React.PropTypes.number,
|
||||
code: React.PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -40,38 +46,30 @@ export default class LineChart extends TranslatedComponent {
|
||||
super(props);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateSeriesData = this._updateSeriesData.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);
|
||||
|
||||
let markerElems = [];
|
||||
let detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
|
||||
let xScale = d3.scale.linear();
|
||||
let xAxisScale = d3.scale.linear();
|
||||
let yScale = d3.scale.linear();
|
||||
let series = props.series;
|
||||
let seriesLines = [];
|
||||
const series = props.series;
|
||||
|
||||
this.xAxis = d3.svg.axis().scale(xAxisScale).outerTickSize(0).orient('bottom');
|
||||
this.yAxis = d3.svg.axis().scale(yScale).ticks(6).outerTickSize(0).orient('left');
|
||||
let xScale = d3.scaleLinear();
|
||||
let yScale = d3.scaleLinear();
|
||||
let xAxisScale = d3.scaleLinear();
|
||||
|
||||
for(let i = 0, l = series ? series.length : 1; i < l; i++) {
|
||||
let yAccessor = series ? function(d) { return yScale(d[1][this]); }.bind(series[i]) : (d) => yScale(d[1]);
|
||||
seriesLines.push(d3.svg.line().x((d) => xScale(d[0])).y(yAccessor));
|
||||
detailElems.push(<text key={i} className='text-tip y' y={1.25 * (i + 2) + 'em'}/>);
|
||||
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||
}
|
||||
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
|
||||
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
|
||||
|
||||
this.state = {
|
||||
xScale,
|
||||
xAxisScale,
|
||||
yScale,
|
||||
seriesLines,
|
||||
detailElems,
|
||||
markerElems,
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8))
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||
dimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -81,13 +79,14 @@ export default class LineChart extends TranslatedComponent {
|
||||
*/
|
||||
_tooltip(xPos) {
|
||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||
let { xScale, yScale, innerWidth } = this.state;
|
||||
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 / innerWidth > 0.60),
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
@@ -98,7 +97,7 @@ export default class LineChart extends TranslatedComponent {
|
||||
let yVal = series ? y0[series[i]] : y0;
|
||||
yTotal += yVal;
|
||||
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||
}).append('tspan').attr('class', 'metric').text(' ' + yUnit);
|
||||
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
|
||||
|
||||
tips.selectAll('text').each(function() {
|
||||
if (this.getBBox().width > tipWidth) {
|
||||
@@ -118,19 +117,21 @@ export default class LineChart extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Update dimensions based on properties and scale
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} props React Component properties
|
||||
* @param {number} scale size ratio / scale
|
||||
* @returns {Object} calculated dimensions
|
||||
*/
|
||||
_updateDimensions(props, scale) {
|
||||
let { width, xMax, xMin, yMin, yMax } = props;
|
||||
let innerWidth = width - MARGIN.left - MARGIN.right;
|
||||
let outerHeight = Math.round(width * 0.5 * scale);
|
||||
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
|
||||
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]);
|
||||
this.setState({ innerWidth, outerHeight, innerHeight });
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,35 +165,47 @@ export default class LineChart extends TranslatedComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update series data generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* Update series generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} state React Component state
|
||||
*/
|
||||
_updateSeriesData(props) {
|
||||
let { func, xMin, xMax, series } = props;
|
||||
let delta = (xMax - xMin) / RENDER_POINTS;
|
||||
let seriesData = new Array(RENDER_POINTS);
|
||||
_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(RENDER_POINTS);
|
||||
for (let i = 0, x = xMin; i < RENDER_POINTS; i++) {
|
||||
seriesData = new Array(points);
|
||||
for (let i = 0, x = xMin; i < points; i++) {
|
||||
seriesData[i] = [x, func(x)];
|
||||
x += delta;
|
||||
}
|
||||
seriesData[RENDER_POINTS - 1] = [xMax, func(xMax)];
|
||||
seriesData[points - 1] = [xMax, func(xMax)];
|
||||
} else {
|
||||
let yVal = func(xMin);
|
||||
seriesData = [[0, yVal], [1, yVal]];
|
||||
}
|
||||
|
||||
this.setState({ seriesData });
|
||||
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._updateDimensions(this.props, this.context.sizeRatio);
|
||||
this._updateSeriesData(this.props);
|
||||
this._updateSeries(this.props, this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,17 +214,10 @@ export default class LineChart extends TranslatedComponent {
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
let { func, xMin, xMax, yMin, yMax, width } = nextProps;
|
||||
let props = this.props;
|
||||
const props = this.props;
|
||||
|
||||
let domainChanged = xMax != props.xMax || xMin != props.xMin || yMax != props.yMax || yMin != props.yMin || func != props.func;
|
||||
|
||||
if (width != props.width || domainChanged || this.context.sizeRatio != nextContext.sizeRatio) {
|
||||
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||
}
|
||||
|
||||
if (domainChanged) {
|
||||
this._updateSeriesData(nextProps);
|
||||
if (props.code != nextProps.code) {
|
||||
this._updateSeries(nextProps, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,49 +226,57 @@ export default class LineChart extends TranslatedComponent {
|
||||
* @return {React.Component} Chart SVG
|
||||
*/
|
||||
render() {
|
||||
if (!this.props.width) {
|
||||
return null;
|
||||
}
|
||||
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();
|
||||
|
||||
let { xLabel, yLabel, xUnit, yUnit, colors } = this.props;
|
||||
let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||
let line = this.line;
|
||||
let lines = seriesLines.map((line, i) => <path key={i} className='line' stroke={colors[i]} strokeWidth='2' d={line(seriesData)} />);
|
||||
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 <svg style={{ width: '100%', height: outerHeight }}>
|
||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||
<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>
|
||||
<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>;
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class Link extends React.Component {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
handler(event) {
|
||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 1) {
|
||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 0) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
48
src/app/components/ModalHelp.jsx
Normal file
48
src/app/components/ModalHelp.jsx
Normal file
@@ -0,0 +1,48 @@
|
||||
/* eslint react/no-danger: 0 */
|
||||
import React from 'react';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
/**
|
||||
* Help Modal
|
||||
*/
|
||||
export default class ModalHelp extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: React.PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus on textarea and select all
|
||||
*/
|
||||
componentDidMount() {
|
||||
const e = findDOMNode(this.refs.exportField);
|
||||
if (e) {
|
||||
e.focus();
|
||||
e.select();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modal
|
||||
* @return {React.Component} Modal Content
|
||||
*/
|
||||
render() {
|
||||
const translate = this.context.language.translate;
|
||||
const text = translate('HELP_TEXT');
|
||||
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2>{translate(this.props.title || 'Help')}</h2>
|
||||
<div dangerouslySetInnerHTML={{ __html: text }} />
|
||||
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
||||
import { Download } from './SvgIcons';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||
|
||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||
@@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this._importBackup = this._importBackup.bind(this);
|
||||
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||
this._importTextBuild = this._importTextBuild.bind(this);
|
||||
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
||||
this._validateImport = this._validateImport.bind(this);
|
||||
}
|
||||
|
||||
@@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent {
|
||||
this.setState({ builds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a build direct from the companion API
|
||||
* @param {string} build JSON from the companion API information
|
||||
* @throws {string} if parse/import fails
|
||||
*/
|
||||
_importCompanionApiBuild(build) {
|
||||
const shipModel = CompanionApiUtils.shipModelFromJson(build);
|
||||
const ship = CompanionApiUtils.shipFromJson(build);
|
||||
|
||||
let builds = {};
|
||||
builds[shipModel] = {};
|
||||
builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString();
|
||||
this.setState({ builds, singleBuild: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a text build from ED Shipyard
|
||||
* @param {string} buildStr Build string
|
||||
@@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent {
|
||||
throw 'Must be an object or array!';
|
||||
}
|
||||
|
||||
if (importData instanceof Array) { // Must be detailed export json
|
||||
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData); // Single sihp definition
|
||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
||||
} else if (importData instanceof Array) { // Must be detailed export json
|
||||
this._importDetailedArray(importData);
|
||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||
@@ -325,6 +346,7 @@ export default class ModalImport extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e.stack);
|
||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
||||
return;
|
||||
}
|
||||
|
||||
94
src/app/components/Modification.jsx
Normal file
94
src/app/components/Modification.jsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import NumberEditor from 'react-number-editor';
|
||||
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class Modification extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
m: React.PropTypes.object.isRequired,
|
||||
name: React.PropTypes.string.isRequired,
|
||||
value: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state.value = props.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification given a value.
|
||||
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
|
||||
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
|
||||
* in a value by hand
|
||||
*/
|
||||
_updateValue(value) {
|
||||
const name = this.props.name;
|
||||
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -99.99%
|
||||
if (scaledValue > 100000) {
|
||||
scaledValue = 100000;
|
||||
value = 1000;
|
||||
}
|
||||
if (scaledValue < -9999) {
|
||||
scaledValue = -9999;
|
||||
value = -99.99;
|
||||
}
|
||||
|
||||
let m = this.props.m;
|
||||
let ship = this.props.ship;
|
||||
ship.setModification(m, name, scaledValue, true);
|
||||
|
||||
this.setState({ value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
||||
*/
|
||||
_updateFinished() {
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modification
|
||||
* @return {React.Component} modification
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let { m, name } = this.props;
|
||||
|
||||
if (name === 'damagedist') {
|
||||
// We don't show damage distribution
|
||||
return null;
|
||||
}
|
||||
|
||||
let symbol;
|
||||
if (name === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (name !== 'burst' && name != 'burstrof') {
|
||||
symbol = '%';
|
||||
}
|
||||
if (symbol) {
|
||||
symbol = ' (' + symbol + ')';
|
||||
}
|
||||
|
||||
return (
|
||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
|
||||
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
350
src/app/components/ModificationsMenu.jsx
Normal file
350
src/app/components/ModificationsMenu.jsx
Normal file
@@ -0,0 +1,350 @@
|
||||
import React from 'react';
|
||||
import * as _ from 'lodash';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import Modification from './Modification';
|
||||
import { getBlueprint, blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
m: React.PropTypes.object.isRequired,
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
||||
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
||||
this._rollWorst = this._rollWorst.bind(this);
|
||||
this._rollRandom = this._rollRandom.bind(this);
|
||||
this._rollBest = this._rollBest.bind(this);
|
||||
this._rollExtreme = this._rollExtreme.bind(this);
|
||||
this._reset = this._reset.bind(this);
|
||||
|
||||
this.state = {
|
||||
blueprintMenuOpened: false,
|
||||
specialMenuOpened: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the blueprints
|
||||
* @param {Object} props React component properties
|
||||
* @param {Object} context React component context
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderBlueprints(props, context) {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const blueprints = [];
|
||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
||||
const blueprint = getBlueprint(blueprintName, m);
|
||||
let blueprintGrades = [];
|
||||
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
|
||||
// Grade is a string in the JSON so make it a number
|
||||
grade = Number(grade);
|
||||
const classes = cn('c', {
|
||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
||||
});
|
||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
||||
const key = blueprintName + ':' + grade;
|
||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
||||
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
|
||||
}
|
||||
if (blueprintGrades) {
|
||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
||||
}
|
||||
}
|
||||
return blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the specials
|
||||
* @param {Object} props React component properties
|
||||
* @param {Object} context React component context
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderSpecials(props, context) {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
|
||||
const specials = [];
|
||||
if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) {
|
||||
const close = this._specialSelected.bind(this, null);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
for (const specialName of Modifications.modules[m.grp].specials) {
|
||||
const close = this._specialSelected.bind(this, specialName);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
}
|
||||
}
|
||||
return specials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modifications
|
||||
* @param {Object} props React Component properties
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderModifications(props) {
|
||||
const { m, onChange, ship } = props;
|
||||
const modifications = [];
|
||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
||||
if (!Modifications.modifications[modName].hidden) {
|
||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the blueprints menu
|
||||
*/
|
||||
_toggleBlueprintsMenu() {
|
||||
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
|
||||
this.setState({ blueprintMenuOpened });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activated when a blueprint is selected
|
||||
* @param {int} fdname The Frontier name of the blueprint
|
||||
* @param {int} grade The grade of the selected blueprint
|
||||
*/
|
||||
_blueprintSelected(fdname, grade) {
|
||||
this.context.tooltip(null);
|
||||
const { m, ship } = this.props;
|
||||
const blueprint = getBlueprint(fdname, m);
|
||||
blueprint.grade = grade;
|
||||
ship.setModuleBlueprint(m, blueprint);
|
||||
|
||||
this.setState({ blueprintMenuOpened: false });
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the specials menu
|
||||
*/
|
||||
_toggleSpecialsMenu() {
|
||||
const specialMenuOpened = !this.state.specialMenuOpened;
|
||||
this.setState({ specialMenuOpened });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activated when a special is selected
|
||||
* @param {int} special The name of the selected special
|
||||
*/
|
||||
_specialSelected(special) {
|
||||
this.context.tooltip(null);
|
||||
const { m, ship } = this.props;
|
||||
|
||||
if (special === null) {
|
||||
ship.clearModuelSpecial(m);
|
||||
} else {
|
||||
ship.setModuleSpecial(m, Modifications.specials[special]);
|
||||
}
|
||||
|
||||
this.setState({ specialMenuOpened: false });
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the result of a roll
|
||||
* @param {object} ship The ship to which the roll applies
|
||||
* @param {object} m The module to which the roll applies
|
||||
* @param {string} featureName The modification feature to which the roll applies
|
||||
* @param {number} value The value of the roll
|
||||
*/
|
||||
_setRollResult(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 a 'worst' roll within the information we have
|
||||
*/
|
||||
_rollWorst() {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value = features[featureName][0];
|
||||
this._setRollResult(ship, m, featureName, value);
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a random roll within the information we have
|
||||
*/
|
||||
_rollRandom() {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0]));
|
||||
this._setRollResult(ship, m, featureName, value);
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a 'best' roll within the information we have
|
||||
*/
|
||||
_rollBest() {
|
||||
const { m, ship } = this.props;
|
||||
const features = m.blueprint.grades[m.blueprint.grade].features;
|
||||
for (const featureName in features) {
|
||||
let value = features[featureName][1];
|
||||
this._setRollResult(ship, m, featureName, value);
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an 'extreme' roll within the information we have
|
||||
*/
|
||||
_rollExtreme() {
|
||||
const { m, ship } = this.props;
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
this._setRollResult(ship, m, featureName, value);
|
||||
}
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset modification information
|
||||
*/
|
||||
_reset() {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
ship.clearModuleBlueprint(m);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list
|
||||
* @return {React.Component} List
|
||||
*/
|
||||
render() {
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const translate = language.translate;
|
||||
const { m } = this.props;
|
||||
const { blueprintMenuOpened, specialMenuOpened } = this.state;
|
||||
|
||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
||||
const _rollBest = this._rollBest;
|
||||
const _rollExtreme = this._rollExtreme;
|
||||
const _rollWorst = this._rollWorst;
|
||||
const _rollRandom = this._rollRandom;
|
||||
const _reset = this._reset;
|
||||
|
||||
let blueprintLabel;
|
||||
let haveBlueprint = false;
|
||||
let blueprintTt;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
haveBlueprint = true;
|
||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
|
||||
}
|
||||
|
||||
let specialLabel;
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
specialLabel = m.blueprint.special.name;
|
||||
} else {
|
||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||
}
|
||||
|
||||
const specials = this._renderSpecials(this.props, this.context);
|
||||
|
||||
const showBlueprintsMenu = blueprintMenuOpened;
|
||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
||||
const showSpecialsMenu = specialMenuOpened;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showReset = !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showMods = !blueprintMenuOpened && !specialMenuOpened;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{ showBlueprintsMenu ? '' : haveBlueprint ?
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
||||
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
|
||||
{ showSpecialsMenu ? specials : null }
|
||||
{ showRolls || showReset ?
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<tr>
|
||||
<td> { translate('roll') }: </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
{ showReset ?
|
||||
<tr>
|
||||
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ showMods ?
|
||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
||||
{ this._renderModifications(this.props) }
|
||||
</span> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
70
src/app/components/Movement.jsx
Normal file
70
src/app/components/Movement.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
/**
|
||||
* Movement
|
||||
*/
|
||||
export default class Movement extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
boost: React.PropTypes.bool.isRequired,
|
||||
eng: React.PropTypes.number.isRequired,
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
cargo: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render movement
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, boost, eng, cargo, fuel } = this.props;
|
||||
const { language } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
|
||||
return (
|
||||
<span id='movement'>
|
||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||
// Axes
|
||||
<path d="M150 250v300" strokeWidth='1'/>
|
||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||
<path d="M150 250l350 -200" strokeWidth='1'/>
|
||||
// End Arrow
|
||||
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
||||
// 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"/>
|
||||
<path d="M174 450.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
|
||||
|
||||
<path d="M407.7 119c2 .7 4.3 1 6.4 1 14 0 25-11.2 25-25s-11-25-25-25c-11 0-21 7.6-24 18.5 3-11 13-18.5 24-18.5 14 0 25 11.2 25 25s-11 25-25 25c-2 0-4-.3-6-1z" strokeWidth='2'/>
|
||||
<path d="M388 99.7L387 84l9.8 2.5-8.7 13.2z"/>
|
||||
<path d="M398.8 85.5l.2.5-10.7 16-1.6-.3-1.2-19.3.4-.3 12.5 3.8zm-9.5 9.7l5-7.7-5.6-1.6.6 9zm10 20.8l15.7-1-2.6 9.7-13.2-8.8z"/>
|
||||
<path d="M417 113.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
|
||||
|
||||
<path d="M355 430c0-13.8-11.2-25-25-25s-25 11.2-25 25 11.2 25 25 25c-13.8 0-25-11.2-25-25s11.2-25 25-25 25 11.2 25 25z" strokeWidth='2'/>
|
||||
<path d="M357 439.7l-8.8-13 9.7-2.7-1 15.7z"/>
|
||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||
|
||||
// Speed
|
||||
<text x="470" y="30" strokeWidth='0'>{formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s</text>
|
||||
// Pitch
|
||||
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s</text>
|
||||
// Roll
|
||||
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s</text>
|
||||
// Yaw
|
||||
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s</text>
|
||||
</svg>
|
||||
</span>);
|
||||
}
|
||||
}
|
||||
266
src/app/components/Offence.jsx
Normal file
266
src/app/components/Offence.jsx
Normal file
@@ -0,0 +1,266 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
|
||||
/**
|
||||
* Generates an internationalization friendly weapon comparator that will
|
||||
* sort by specified property (if provided) then by name/group, class, rating
|
||||
* @param {function} translate Translation function
|
||||
* @param {function} propComparator Optional property comparator
|
||||
* @param {boolean} desc Use descending order
|
||||
* @return {function} Comparator function for names
|
||||
*/
|
||||
export function weaponComparator(translate, propComparator, desc) {
|
||||
return (a, b) => {
|
||||
if (!desc) { // Flip A and B if ascending order
|
||||
let t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
|
||||
// If a property comparator is provided use it first
|
||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
||||
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Property matches so sort by name / group, then class, rating
|
||||
if (a.name === b.name && a.grp === b.grp) {
|
||||
if(a.class == b.class) {
|
||||
return a.rating > b.rating ? 1 : -1;
|
||||
}
|
||||
return a.class - b.class;
|
||||
}
|
||||
|
||||
return nameComparator(translate, a, b);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Offence information
|
||||
* Offence information consists of four panels:
|
||||
* - textual information (time to drain cap, time to take down shields etc.)
|
||||
* - breakdown of damage sources (pie chart)
|
||||
* - comparison of shield resistances (table chart)
|
||||
* - effective sustained DPS of weapons (bar chart)
|
||||
*/
|
||||
export default class Offence extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
opponent: React.PropTypes.object.isRequired,
|
||||
engagementrange: React.PropTypes.number.isRequired,
|
||||
wep: React.PropTypes.number.isRequired,
|
||||
opponentSys: React.PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
damage
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our properties change
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
|
||||
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
|
||||
this.setState({ damage });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order and sort
|
||||
* @param {string} predicate Sort predicate
|
||||
*/
|
||||
_sortOrder(predicate) {
|
||||
let desc = this.state.desc;
|
||||
|
||||
if (predicate == this.state.predicate) {
|
||||
desc = !desc;
|
||||
} else {
|
||||
desc = true;
|
||||
}
|
||||
|
||||
this._sort(predicate, desc);
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the weapon list
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
*/
|
||||
_sort(predicate, desc) {
|
||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
||||
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
|
||||
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
|
||||
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
|
||||
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
|
||||
}
|
||||
|
||||
this.state.damage.sort(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, opponent, wep, engagementrange } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { damage } = this.state;
|
||||
const sortOrder = this._sortOrder;
|
||||
|
||||
const pd = ship.standard[4].m;
|
||||
|
||||
const opponentShields = Calc.shieldMetrics(opponent, 4);
|
||||
const opponentArmour = Calc.armourMetrics(opponent);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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 effectivenessArmourTooltipDetails = [];
|
||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
||||
const effectiveArmourSDpsTooltipDetails = [];
|
||||
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
|
||||
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
|
||||
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
|
||||
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
|
||||
|
||||
rows.push(
|
||||
<tr key={weapon.id}>
|
||||
<td className='ri'>
|
||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||
{weapon.classRating} {translate(weapon.name)}
|
||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
||||
</td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
||||
</tr>);
|
||||
}
|
||||
|
||||
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
|
||||
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
|
||||
|
||||
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 armourSDpsData = [];
|
||||
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
|
||||
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
|
||||
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
|
||||
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
|
||||
|
||||
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
|
||||
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>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('offence metrics')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
|
||||
</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} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
|
||||
<PieChart data={armourSDpsData} />
|
||||
</div>
|
||||
</span>);
|
||||
}
|
||||
}
|
||||
186
src/app/components/OutfittingSubpages.jsx
Normal file
186
src/app/components/OutfittingSubpages.jsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
import CostSection from './CostSection';
|
||||
import EngineProfile from './EngineProfile';
|
||||
import FSDProfile from './FSDProfile';
|
||||
import Movement from './Movement';
|
||||
import Offence from './Offence';
|
||||
import Defence from './Defence';
|
||||
import WeaponDamageChart from './WeaponDamageChart';
|
||||
|
||||
/**
|
||||
* Outfitting subpages
|
||||
*/
|
||||
export default class OutfittingSubpages extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
buildName: React.PropTypes.string,
|
||||
sys: React.PropTypes.number.isRequired,
|
||||
eng: React.PropTypes.number.isRequired,
|
||||
wep: React.PropTypes.number.isRequired,
|
||||
cargo: React.PropTypes.number.isRequired,
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
boost: React.PropTypes.bool.isRequired,
|
||||
engagementRange: React.PropTypes.number.isRequired,
|
||||
opponent: React.PropTypes.object.isRequired,
|
||||
opponentBuild: React.PropTypes.string,
|
||||
opponentSys: React.PropTypes.number.isRequired,
|
||||
opponentEng: React.PropTypes.number.isRequired,
|
||||
opponentWep: React.PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._powerTab = this._powerTab.bind(this);
|
||||
this._profilesTab = this._profilesTab.bind(this);
|
||||
this._offenceTab = this._offenceTab.bind(this);
|
||||
this._defenceTab = this._defenceTab.bind(this);
|
||||
|
||||
this.state = {
|
||||
tab: Persist.getOutfittingTab() || 'power',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Show selected tab
|
||||
* @param {string} tab Tab name
|
||||
*/
|
||||
_showTab(tab) {
|
||||
this.setState({ tab });
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the power tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_powerTab() {
|
||||
let { ship, buildName, code, onChange } = this.props;
|
||||
Persist.setOutfittingTab('power');
|
||||
|
||||
const powerMarker = `${ship.toString()}`;
|
||||
const costMarker = `${ship.toString().split('.')[0]}`;
|
||||
|
||||
return <div>
|
||||
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
|
||||
<CostSection ship={ship} buildName={buildName} code={costMarker} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the profiles tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_profilesTab() {
|
||||
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
|
||||
const { translate } = this.context.language;
|
||||
let realBoost = boost && ship.canBoost(cargo, fuel);
|
||||
Persist.setOutfittingTab('profiles');
|
||||
|
||||
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
|
||||
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
|
||||
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
|
||||
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
|
||||
|
||||
return <div>
|
||||
<div className='group third'>
|
||||
<h1>{translate('engine profile')}</h1>
|
||||
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('fsd profile')}</h1>
|
||||
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('movement profile')}</h1>
|
||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
|
||||
</div>
|
||||
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s shields')}</h1>
|
||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
|
||||
</div>
|
||||
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s hull')}</h1>
|
||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the offence tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_offenceTab() {
|
||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
|
||||
Persist.setOutfittingTab('offence');
|
||||
|
||||
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
|
||||
|
||||
return <div>
|
||||
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the defence tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_defenceTab() {
|
||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
|
||||
Persist.setOutfittingTab('defence');
|
||||
|
||||
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
|
||||
|
||||
return <div>
|
||||
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the section
|
||||
* @return {React.Component} Contents
|
||||
*/
|
||||
render() {
|
||||
const tab = this.state.tab;
|
||||
const translate = this.context.language.translate;
|
||||
let tabSection;
|
||||
|
||||
switch (tab) {
|
||||
case 'power': tabSection = this._powerTab(); break;
|
||||
case 'profiles': tabSection = this._profilesTab(); break;
|
||||
case 'offence': tabSection = this._offenceTab(); break;
|
||||
case 'defence': tabSection = this._defenceTab(); break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||
<table className='tabs'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
{tabSection}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
96
src/app/components/PieChart.jsx
Normal file
96
src/app/components/PieChart.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import React, { Component } from 'react';
|
||||
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 : React.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>
|
||||
);
|
||||
}
|
||||
}
|
||||
298
src/app/components/Pips.jsx
Normal file
298
src/app/components/Pips.jsx
Normal file
@@ -0,0 +1,298 @@
|
||||
import React from 'react';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||
* Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change.
|
||||
*/
|
||||
export default class Pips extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
sys: React.PropTypes.number.isRequired,
|
||||
eng: React.PropTypes.number.isRequired,
|
||||
wep: React.PropTypes.number.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
const { sys, eng, wep } = props;
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners after mounting
|
||||
*/
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners before unmounting
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Key Down
|
||||
* @param {Event} e Keyboard Event
|
||||
*/
|
||||
_keyDown(e) {
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
|
||||
switch (e.keyCode) {
|
||||
case 37: // Left arrow == increase SYS
|
||||
e.preventDefault();
|
||||
this._incSys();
|
||||
break;
|
||||
case 38: // Up arrow == increase ENG
|
||||
e.preventDefault();
|
||||
this._incEng();
|
||||
break;
|
||||
case 39: // Right arrow == increase WEP
|
||||
e.preventDefault();
|
||||
this._incWep();
|
||||
break;
|
||||
case 40: // Down arrow == reset
|
||||
e.preventDefault();
|
||||
this._reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
sys = eng = wep = 2;
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the SYS capacitor
|
||||
*/
|
||||
_incSys() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - sys);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (eng > wep) {
|
||||
eng -= 0.5;
|
||||
sys += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
sys += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (eng == 0) {
|
||||
wep -= 1;
|
||||
sys += 1;
|
||||
} else if (wep == 0) {
|
||||
eng -= 1;
|
||||
sys += 1;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep -= 0.5;
|
||||
sys += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the ENG capacitor
|
||||
*/
|
||||
_incEng() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - eng);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > wep) {
|
||||
sys -= 0.5;
|
||||
eng += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
eng += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
wep -= 1;
|
||||
eng += 1;
|
||||
} else if (wep == 0) {
|
||||
sys -= 1;
|
||||
eng += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
wep -= 0.5;
|
||||
eng += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the WEP capacitor
|
||||
*/
|
||||
_incWep() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - wep);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > eng) {
|
||||
sys -= 0.5;
|
||||
wep += 0.5;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
eng -= 1;
|
||||
wep += 1;
|
||||
} else if (eng == 0) {
|
||||
sys -= 1;
|
||||
wep += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
eng -= 0.5;
|
||||
wep += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the rendering for pips
|
||||
* @param {int} sys the SYS pips
|
||||
* @param {int} eng the ENG pips
|
||||
* @param {int} wep the WEP pips
|
||||
* @returns {Object} Object containing the rendering for the pips
|
||||
*/
|
||||
_renderPips(sys, eng, wep) {
|
||||
const pipsSvg = {};
|
||||
|
||||
// SYS
|
||||
pipsSvg['SYS'] = [];
|
||||
for (let i = 0; i < Math.floor(sys); i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (sys > Math.floor(sys)) {
|
||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
|
||||
// ENG
|
||||
pipsSvg['ENG'] = [];
|
||||
for (let i = 0; i < Math.floor(eng); i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (eng > Math.floor(eng)) {
|
||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
|
||||
// WEP
|
||||
pipsSvg['WEP'] = [];
|
||||
for (let i = 0; i < Math.floor(wep); i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (wep > Math.floor(wep)) {
|
||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
|
||||
return pipsSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render pips
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { sys, eng, wep } = 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);
|
||||
return (
|
||||
<span id='pips'>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onEngClicked}>{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>
|
||||
</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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import * as d3 from 'd3';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
@@ -46,10 +46,10 @@ export default class PowerBands extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.wattScale = d3.scale.linear();
|
||||
this.pctScale = d3.scale.linear().domain([0, 1]);
|
||||
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
|
||||
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
|
||||
this.wattScale = d3.scaleLinear();
|
||||
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
||||
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
||||
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateScales = this._updateScales.bind(this);
|
||||
@@ -186,10 +186,10 @@ export default class PowerBands extends TranslatedComponent {
|
||||
|
||||
let { wattScale, pctScale, context, props, state } = this;
|
||||
let { translate, formats } = context.language;
|
||||
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
|
||||
let { available, bands, width } = props;
|
||||
let { innerWidth, maxPwr, ret, dep } = state;
|
||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
|
||||
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
||||
let { available, bands } = props;
|
||||
let { innerWidth, ret, dep } = state;
|
||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
|
||||
let deployed = [];
|
||||
let retracted = [];
|
||||
let retSelected = Object.keys(ret).length > 0;
|
||||
@@ -268,7 +268,7 @@ export default class PowerBands extends TranslatedComponent {
|
||||
axis.call(this.pctAxis);
|
||||
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
|
||||
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
|
||||
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
||||
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
||||
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>
|
||||
|
||||
@@ -17,7 +17,7 @@ const POWER = [
|
||||
* Power Management Section
|
||||
*/
|
||||
export default class PowerManagement extends TranslatedComponent {
|
||||
static PropTypes = {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
code: React.PropTypes.string.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired
|
||||
@@ -34,8 +34,8 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
predicate: 'pwr',
|
||||
desc: false,
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
||||
case 'pwr': comp = comp((a, b) => a.m.power - b.m.power, desc); break;
|
||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
||||
let slot = ship.powerList[i];
|
||||
|
||||
if (slot.m && slot.m.power) {
|
||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
||||
let m = slot.m;
|
||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
||||
let retractedElem = null, deployedElem = null;
|
||||
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
{' ' + (slot.priority + 1) + ' '}
|
||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
||||
</td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
|
||||
{retractedElem}
|
||||
{deployedElem}
|
||||
</tr>);
|
||||
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
<td className='le shorten cap' >{translate('pp')}</td>
|
||||
<td><u >{translate('SYS')}</u></td>
|
||||
<td>1</td>
|
||||
<td className='ri'>{pwr(pp.pGen)}</td>
|
||||
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
|
||||
<td className='ri'><u>100%</u></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
||||
</tbody>
|
||||
</table>
|
||||
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} />
|
||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
126
src/app/components/ShipPicker.jsx
Normal file
126
src/app/components/ShipPicker.jsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { Rocket } from './SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import cn from 'classnames';
|
||||
|
||||
/**
|
||||
* Ship picker
|
||||
* Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change
|
||||
*/
|
||||
export default class ShipPicker extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
ship: React.PropTypes.string.isRequired,
|
||||
build: React.PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
ship: 'eagle'
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.shipOrder = Object.keys(Ships).sort();
|
||||
this._toggleMenu = this._toggleMenu.bind(this);
|
||||
this._closeMenu = this._closeMenu.bind(this);
|
||||
|
||||
this.state = { menuOpen: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ship
|
||||
* @param {object} ship the ship
|
||||
* @param {string} build the build, if present
|
||||
*/
|
||||
_shipChange(ship, build) {
|
||||
this._closeMenu();
|
||||
|
||||
// Ensure that the ship has changed
|
||||
if (ship !== this.props.ship || build !== this.props.build) {
|
||||
this.props.onChange(ship, build);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the menu for the picker
|
||||
* @returns {object} the picker menu
|
||||
*/
|
||||
_renderPickerMenu() {
|
||||
const { ship, build } = this.props;
|
||||
const _shipChange = this._shipChange;
|
||||
const builds = Persist.getBuilds();
|
||||
const buildList = [];
|
||||
for (let shipId of this.shipOrder) {
|
||||
const shipBuilds = [];
|
||||
// Add stock build
|
||||
const stockSelected = (ship == shipId && !build);
|
||||
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
|
||||
if (builds[shipId]) {
|
||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
||||
for (let buildName of buildNameOrder) {
|
||||
const buildSelected = ship === shipId && build === buildName;
|
||||
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
|
||||
}
|
||||
}
|
||||
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
||||
}
|
||||
|
||||
return buildList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the menu state
|
||||
*/
|
||||
_toggleMenu() {
|
||||
const { menuOpen } = this.state;
|
||||
this.setState({ menuOpen: !menuOpen });
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the menu
|
||||
*/
|
||||
_closeMenu() {
|
||||
const { menuOpen } = this.state;
|
||||
if (menuOpen) {
|
||||
this._toggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render picker
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, build } = this.props;
|
||||
const { menuOpen } = this.state;
|
||||
|
||||
const shipString = ship + ': ' + (build ? build : translate('stock'));
|
||||
return (
|
||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||
<div className='menu'>
|
||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||
<span><Rocket className='warning' /></span>
|
||||
<span className='menu-item-label'>{shipString}</span>
|
||||
</div>
|
||||
{ menuOpen ?
|
||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
||||
<div className='quad'>
|
||||
{this._renderPickerMenu()}
|
||||
</div>
|
||||
</div> : null }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import { SizeMap } from '../shipyard/Constants';
|
||||
import { Warning } from './SvgIcons';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
* Ship Summary Table / Stats
|
||||
@@ -10,7 +10,10 @@ import { Warning } from './SvgIcons';
|
||||
export default class ShipSummaryTable extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
cargo: React.PropTypes.number.isRequired,
|
||||
fuel: React.PropTypes.number.isRequired,
|
||||
marker: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -18,86 +21,76 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
* @return {React.Component} Summary table
|
||||
*/
|
||||
render() {
|
||||
let ship = this.props.ship;
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
let { language, tooltip, termtip } = this.context;
|
||||
let translate = language.translate;
|
||||
let u = language.units;
|
||||
let formats = language.formats;
|
||||
let round = formats.round;
|
||||
let { time, int } = formats;
|
||||
let armourDetails = null;
|
||||
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
|
||||
let sgRecover = '-';
|
||||
let sgRecharge = '-';
|
||||
let { time, int, round, f1, f2 } = formats;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
if (ship.armourMultiplier > 1 || ship.armourAdded) {
|
||||
armourDetails = <u>({
|
||||
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
|
||||
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
|
||||
})</u>;
|
||||
}
|
||||
|
||||
if (ship.shieldStrength) {
|
||||
sgRecover = time(ship.calcShieldRecovery());
|
||||
sgRecharge = time(ship.calcShieldRecharge());
|
||||
}
|
||||
const shieldGenerator = ship.findInternalByGroup('sg');
|
||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
||||
const canThrust = ship.canThrust(cargo, fuel);
|
||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
const canBoost = ship.canBoost(cargo, fuel);
|
||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
|
||||
return <div id='summary'>
|
||||
<table id='summaryTable'>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2}>{translate('size')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('armour')}</th>
|
||||
<th colSpan={3}>{translate('shields')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5}>{translate('jump range')}</th>
|
||||
<th rowSpan={2}>{translate('shield')}</th>
|
||||
<th rowSpan={2}>{translate('integrity')}</th>
|
||||
<th rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('EPS')}</th>
|
||||
<th rowSpan={2}>{translate('TTD')}</th>
|
||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
<th colSpan={3}>{translate('jump range')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_FASTEST_RANGE')} onMouseLeave={hide} colSpan={3}>{translate('fastest range')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</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('strength')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th>
|
||||
<th className='lft'>{translate('max')}</th>
|
||||
<th>{translate('full tank')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
<th className='lft'>{translate('jumps')}</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 className='cap'>{translate(SizeMap[ship.class])}</td>
|
||||
<td>{ship.agility}/10</td>
|
||||
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td>{round(ship.totalDps)}</td>
|
||||
<td>{int(ship.armour)} {armourDetails}</td>
|
||||
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td>
|
||||
<td className={sgClassNames}>{sgRecover}</td>
|
||||
<td className={sgClassNames}>{sgRecharge}</td>
|
||||
<td>{ship.hullMass} {u.T}</td>
|
||||
<td>{round(ship.unladenMass)} {u.T}</td>
|
||||
<td>{round(ship.ladenMass)} {u.T}</td>
|
||||
<td>{round(ship.cargoCapacity)} {u.T}</td>
|
||||
<td>{round(ship.fuelCapacity)} {u.T}</td>
|
||||
<td>{round(ship.unladenRange)} {u.LY}</td>
|
||||
<td>{round(ship.fullTankRange)} {u.LY}</td>
|
||||
<td>{round(ship.ladenRange)} {u.LY}</td>
|
||||
<td>{round(ship.maxJumpCount)}</td>
|
||||
<td>{round(ship.unladenFastestRange)} {u.LY}</td>
|
||||
<td>{round(ship.ladenFastestRange)} {u.LY}</td>
|
||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{ship.crew}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -2,8 +2,10 @@ import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
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
|
||||
@@ -18,6 +20,7 @@ export default class Slot extends TranslatedComponent {
|
||||
selected: React.PropTypes.bool,
|
||||
m: React.PropTypes.object,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
eligible: React.PropTypes.object,
|
||||
warning: React.PropTypes.func,
|
||||
drag: React.PropTypes.func,
|
||||
drop: React.PropTypes.func,
|
||||
@@ -31,6 +34,8 @@ export default class Slot extends TranslatedComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._modificationsSelected = false;
|
||||
|
||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
||||
}
|
||||
@@ -73,25 +78,42 @@ export default class Slot extends TranslatedComponent {
|
||||
render() {
|
||||
let language = this.context.language;
|
||||
let translate = language.translate;
|
||||
let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props;
|
||||
let slotDetails, menu;
|
||||
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
||||
let slotDetails, modificationsMarker, menu;
|
||||
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
if (m) {
|
||||
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
|
||||
modificationsMarker = JSON.stringify(m);
|
||||
} else {
|
||||
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
|
||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
||||
modificationsMarker = '';
|
||||
}
|
||||
|
||||
if (this.props.selected) {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className={this._getClassNames()}
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement touch dragging
|
||||
@@ -100,10 +122,17 @@ export default class Slot extends TranslatedComponent {
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
||||
<div className='details-container'>
|
||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
||||
{slotDetails}
|
||||
</div>
|
||||
{slotDetails}
|
||||
</div>
|
||||
{menu}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
import { Equalizer } from '../components/SvgIcons';
|
||||
import cn from 'classnames';
|
||||
|
||||
@@ -63,7 +64,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
* @param {Object} m Selected module
|
||||
*/
|
||||
_selectModule(slot, m) {
|
||||
this.props.ship.use(slot, m);
|
||||
this.props.ship.use(slot, m, false);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
@@ -76,7 +77,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
_drag(originSlot, e) {
|
||||
e.dataTransfer.setData('text/html', e.currentTarget);
|
||||
e.dataTransfer.effectAllowed = 'all';
|
||||
this.setState({ originSlot });
|
||||
this.setState({ originSlot, copy: e.getModifierState('Alt') });
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -90,7 +91,9 @@ export default class SlotSection extends TranslatedComponent {
|
||||
e.stopPropagation();
|
||||
let os = this.state.originSlot;
|
||||
if (os) {
|
||||
e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none';
|
||||
// Show correct icon
|
||||
const effect = this.state.copy ? 'copy' : 'move';
|
||||
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? effect : 'none';
|
||||
this.setState({ targetSlot });
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
@@ -113,20 +116,30 @@ export default class SlotSection extends TranslatedComponent {
|
||||
* the origin slot will be empty.
|
||||
*/
|
||||
_drop() {
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let { originSlot, targetSlot, copy } = this.state;
|
||||
let m = originSlot.m;
|
||||
|
||||
if (targetSlot && m && targetSlot.maxClass >= m.class) {
|
||||
// Swap modules if possible
|
||||
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) {
|
||||
this.props.ship.use(originSlot, targetSlot.m, true);
|
||||
} else { // Otherwise empty the origin slot
|
||||
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
|
||||
if (copy) {
|
||||
// We want to copy the module in to the target slot
|
||||
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||
const mCopy = m.clone();
|
||||
this.props.ship.use(targetSlot, mCopy, false);
|
||||
this.props.onChange();
|
||||
}
|
||||
} else {
|
||||
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
|
||||
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||
// Swap modules if possible
|
||||
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
|
||||
this.props.ship.use(originSlot, targetSlot.m, true);
|
||||
} else { // Otherwise empty the origin slot
|
||||
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
|
||||
}
|
||||
this.props.ship.use(targetSlot, m); // update target slot
|
||||
this.props.onChange();
|
||||
}
|
||||
this.props.ship.use(targetSlot, m); // update target slot
|
||||
this.props.onChange();
|
||||
}
|
||||
this.setState({ originSlot: null, targetSlot: null });
|
||||
this.setState({ originSlot: null, targetSlot: null, copy: null });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,12 +154,12 @@ export default class SlotSection extends TranslatedComponent {
|
||||
return null;
|
||||
}
|
||||
if (slot === originSlot) {
|
||||
if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) {
|
||||
if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
|
||||
return 'dropEmpty'; // Origin slot will be emptied
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot
|
||||
if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot
|
||||
if (slot === targetSlot) {
|
||||
return 'drop'; // Can drop
|
||||
}
|
||||
@@ -179,7 +192,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
||||
<h1>{translate(this.sectionName)} <Equalizer/></h1>
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
||||
</div>
|
||||
{this._getSlots()}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { jumpRange } from '../shipyard/Calculations';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Standard Slot
|
||||
@@ -15,53 +21,103 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
modules: React.PropTypes.array.isRequired,
|
||||
onSelect: React.PropTypes.func.isRequired,
|
||||
onOpen: React.PropTypes.func.isRequired,
|
||||
onChange: React.PropTypes.func.isRequired,
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
selected: React.PropTypes.bool,
|
||||
warning: React.PropTypes.func,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the slot
|
||||
* @param {object} props Object properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slot
|
||||
* @return {React.Component} Slot component
|
||||
*/
|
||||
render() {
|
||||
let { termtip, tooltip } = this.context;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props;
|
||||
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
|
||||
let m = slot.m;
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
if (this.props.selected) {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ship.ladenMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
const modificationsMarker = JSON.stringify(m);
|
||||
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className='standard'
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}>
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
|
||||
<div className={'sz'}>{slot.maxClass}</div>
|
||||
<div>
|
||||
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
|
||||
<div className={'r'}>{m.mass || m.fuel || 0}{units.T}</div>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
|
||||
<div className={'r'}>{formats.round(mass)}{units.T}</div>
|
||||
<div/>
|
||||
<div className={'cb'}>
|
||||
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
|
||||
{ m.optmass ? <div className='l'>{translate('optimal mass')}: {m.optmass}{units.T}</div> : null }
|
||||
{ m.maxmass ? <div className='l'>{translate('max mass')}: {m.maxmass}{units.T}</div> : null }
|
||||
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
|
||||
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
|
||||
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
||||
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
|
||||
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
||||
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
|
||||
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null }
|
||||
{ m.maxfuel ? <div className='l'>{translate('max')} {translate('fuel')}: {m.maxfuel}{units.T}</div> : null }
|
||||
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
|
||||
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
|
||||
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
|
||||
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
|
||||
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
|
||||
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
|
||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,4 +125,11 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import StandardSlot from './StandardSlot';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
@@ -18,7 +17,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'standard', 'standard');
|
||||
super(props, context, 'standard', 'core internal');
|
||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
||||
this._selectBulkhead = this._selectBulkhead.bind(this);
|
||||
}
|
||||
@@ -53,6 +52,16 @@ export default class StandardSlotSection extends SlotSection {
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Miner Build
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
_optimizeMiner(shielded) {
|
||||
ShipRoles.miner(this.props.ship, shielded);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Explorer role
|
||||
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
||||
@@ -86,12 +95,10 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @return {Array} Array of Slots
|
||||
*/
|
||||
_getSlots() {
|
||||
let { translate, units, formats } = this.context.language;
|
||||
let { ship, currentMenu } = this.props;
|
||||
let { ship, currentMenu, cargo, fuel } = this.props;
|
||||
let slots = new Array(8);
|
||||
let open = this._openMenu;
|
||||
let select = this._selectModule;
|
||||
let selBulkhead = this._selectBulkhead;
|
||||
let st = ship.standard;
|
||||
let avail = ship.getAvailableModules().standard;
|
||||
let bh = ship.bulkheads;
|
||||
@@ -103,6 +110,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, bh)}
|
||||
onSelect={this._selectBulkhead}
|
||||
selected={currentMenu == bh}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
/>;
|
||||
|
||||
@@ -113,8 +121,9 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[0])}
|
||||
onSelect={select.bind(this, st[0])}
|
||||
selected={currentMenu == st[0]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m.pGen < ship.powerRetracted}
|
||||
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
|
||||
/>;
|
||||
|
||||
slots[2] = <StandardSlot
|
||||
@@ -124,8 +133,9 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[1])}
|
||||
onSelect={select.bind(this, st[1])}
|
||||
selected={currentMenu == st[1]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
|
||||
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
|
||||
/>;
|
||||
|
||||
|
||||
@@ -135,6 +145,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
modules={avail[2]}
|
||||
onOpen={open.bind(this, st[2])}
|
||||
onSelect={select.bind(this, st[2])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[2]}
|
||||
/>;
|
||||
@@ -145,6 +156,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
modules={avail[3]}
|
||||
onOpen={open.bind(this, st[3])}
|
||||
onSelect={select.bind(this, st[3])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[3]}
|
||||
/>;
|
||||
@@ -156,8 +168,9 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[4])}
|
||||
onSelect={select.bind(this, st[4])}
|
||||
selected={currentMenu == st[4]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.enginecapacity < ship.boostEnergy}
|
||||
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
|
||||
/>;
|
||||
|
||||
slots[6] = <StandardSlot
|
||||
@@ -167,8 +180,8 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[5])}
|
||||
onSelect={select.bind(this, st[5])}
|
||||
selected={currentMenu == st[5]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.enginecapacity < ship.boostEnergy}
|
||||
/>;
|
||||
|
||||
slots[7] = <StandardSlot
|
||||
@@ -178,6 +191,7 @@ export default class StandardSlotSection extends SlotSection {
|
||||
onOpen={open.bind(this, st[6])}
|
||||
onSelect={select.bind(this, st[6])}
|
||||
selected={currentMenu == st[6]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
|
||||
/>;
|
||||
@@ -204,6 +218,8 @@ export default class StandardSlotSection extends SlotSection {
|
||||
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
|
||||
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
|
||||
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
|
||||
<li className='lc' onClick={this._optimizeMiner.bind(this, false)}>{translate('Miner')}</li>
|
||||
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Shielded Miner')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ export class CoriolisLogo extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g transform='translate(1,1)'>
|
||||
<path stroke='#ff3b00' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
|
||||
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/>
|
||||
<path stroke='#0a8bd6' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
|
||||
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#0a8bd6'/>
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -227,6 +227,26 @@ export class LinkIcon extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shopping icon (dollar sign)
|
||||
*/
|
||||
export class ShoppingIcon extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<path d='M94 188v-17c-9-1-16-3-21-6-6-3-11-7-15-14-4-6-6-14-6-23l17-3c2 9 4 16 7 21 5 6 11 9 18 10v-56c-7-1-14-4-22-8-6-3-10-8-13-13-3-6-4-12-4-19 0-13 4-23 13-31 6-5 15-8 26-9v-8h11v8c10 1 18 4 24 9 8 6 12 15 14 26l-18 3c-1-7-4-12-7-16s-8-6-13-7v50l17 6c6 2 10 5 13 8 4 4 7 8 8 13 2 4 3 10 3 15 0 12-4 22-11 31-8 8-18 12-30 13v17H94zm0-153c-7 1-12 3-16 8-4 4-6 9-6 15s2 11 5 16c4 4 9 7 17 9V35zm11 121a28 28 0 0 0 24-28c-1-6-2-11-6-15-3-4-9-7-18-10v53z'/>
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* No Power - Lightning bolt + no entry
|
||||
*/
|
||||
@@ -319,6 +339,108 @@ export class Warning extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute damage
|
||||
*/
|
||||
export class DamageAbsolute extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thermal damage
|
||||
*/
|
||||
export class DamageThermal extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
|
||||
<path d='M100 20v80' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kinetic damage
|
||||
*/
|
||||
export class DamageKinetic extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
|
||||
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explosive damage
|
||||
*/
|
||||
export class DamageExplosive extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
|
||||
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed mount hardpoint
|
||||
*/
|
||||
@@ -334,11 +456,11 @@ export class MountFixed extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' />
|
||||
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' />
|
||||
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' />
|
||||
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' />
|
||||
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' />
|
||||
<circle cx='100' cy='100' r='76' fillOpacity='0' />
|
||||
<path d='M0 100h48' />
|
||||
<path d='M152 100h48' />
|
||||
<path d='M100 0v48' />
|
||||
<path d='M100 152v48' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -358,8 +480,8 @@ export class MountGimballed extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
|
||||
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
|
||||
<ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
|
||||
<ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
@@ -379,13 +501,64 @@ export class MountTurret extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' />
|
||||
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' />
|
||||
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' />
|
||||
<path d='M40 50 A 40 40 0 0 0 0 90' />
|
||||
<path d='M40 50h40' />
|
||||
<path d='M120 90 A 40 40 0 0 0 80 50' />
|
||||
<path d='M0 90v40' />
|
||||
<path d='M120 90v40' />
|
||||
<path d='M0 120h120' />
|
||||
<path d='M120 90h80' />
|
||||
<path d='M0 160h120' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse section
|
||||
*/
|
||||
export class CollapseSection extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<path d='m 100,180 0,-140' />
|
||||
<path d='m 100,40 25,45' />
|
||||
<path d='m 100,40 -25,45' />
|
||||
<path d='m 20,20 160,0' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand section
|
||||
*/
|
||||
export class ExpandSection extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<path d='m 100,20 0,140' />
|
||||
<path d='m 100,160 25,-45' />
|
||||
<path d='m 100,160 -25,-45' />
|
||||
<path d='m 20,180 160,0' />
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rocket ship
|
||||
*/
|
||||
@@ -399,6 +572,90 @@ export class Rocket extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Help
|
||||
*/
|
||||
export class Help extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <path d="M90.381 140.644a225.03 225.03 0 0 1-.106-5.706c0-7.47 1.058-13.92 3.172-19.346 1.55-4.087 4.052-8.21 7.505-12.367 2.536-3.031 7.1-7.453 13.688-13.267 6.59-5.815 10.872-10.448 12.844-13.9 1.974-3.453 2.96-7.224 2.96-11.312 0-7.4-2.89-13.9-8.667-19.503-5.779-5.602-12.862-8.403-21.248-8.403-8.104 0-14.87 2.536-20.296 7.61-5.427 5.074-8.986 13.003-10.677 23.784L50 65.91c1.762-14.448 6.995-25.511 15.698-33.194 8.704-7.68 20.208-11.522 34.514-11.522 15.152 0 27.237 4.124 36.258 12.369C145.49 41.807 150 51.779 150 63.477c0 6.766-1.585 13.003-4.756 18.712-3.17 5.708-9.372 12.65-18.605 20.824-6.201 5.496-10.253 9.55-12.156 12.156-1.904 2.609-3.312 5.603-4.23 8.986-.915 3.383-1.443 8.88-1.584 16.49H90.38zm-1.163 38.162v-21.67h21.67v21.67h-21.67z"/>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ListModifications (engineers)
|
||||
*/
|
||||
export class ListModifications extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
|
||||
/**
|
||||
* Render the Icon
|
||||
* @return {React.Component} SVG Icon
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
|
||||
{this.svg()}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified (engineers)
|
||||
*/
|
||||
export class Modified extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
|
||||
/**
|
||||
* Render the Icon
|
||||
* @return {React.Component} SVG Icon
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
|
||||
{this.svg()}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
|
||||
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hammer
|
||||
*/
|
||||
@@ -451,6 +708,24 @@ export class Switch extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pip
|
||||
*/
|
||||
export class Pip extends SvgIcon {
|
||||
/**
|
||||
* Overriden view box
|
||||
* @return {String} view box
|
||||
*/
|
||||
viewBox() { return '0 0 200 200'; }
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return <rect x='10' y='10' width='180' height='180'/>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In-game Coriolis Station logo
|
||||
*/
|
||||
|
||||
@@ -68,6 +68,7 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this,h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
@@ -98,15 +99,23 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('sb')}</div>
|
||||
<ul>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
|
||||
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
|
||||
<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>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('cm')}</div>
|
||||
<div className='select-group cap'>{translate('hs')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
||||
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{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>
|
||||
</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>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
107
src/app/components/VerticalBarChart.jsx
Normal file
107
src/app/components/VerticalBarChart.jsx
Normal file
@@ -0,0 +1,107 @@
|
||||
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
|
||||
*/
|
||||
const ValueLabel = React.createClass({
|
||||
propTypes: {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
value: PropTypes.number
|
||||
},
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
203
src/app/components/WeaponDamageChart.jsx
Normal file
203
src/app/components/WeaponDamageChart.jsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import React from 'react';
|
||||
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'];
|
||||
|
||||
/**
|
||||
* Weapon damage chart
|
||||
*/
|
||||
export default class WeaponDamageChart extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: React.PropTypes.object.isRequired,
|
||||
opponent: React.PropTypes.object.isRequired,
|
||||
hull: React.PropTypes.bool.isRequired,
|
||||
engagementRange: React.PropTypes.number.isRequired,
|
||||
opponentSys: React.PropTypes.number.isRequired,
|
||||
marker: React.PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial weapons state
|
||||
*/
|
||||
componentWillMount() {
|
||||
const weaponNames = this._weaponNames(this.props.ship, this.context);
|
||||
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
|
||||
const opponentArmour = Calc.armourMetrics(this.props.opponent);
|
||||
const maxRange = this._calcMaxRange(this.props.ship);
|
||||
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
|
||||
|
||||
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the updated weapons state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
|
||||
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
|
||||
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
|
||||
const maxRange = this._calcMaxRange(nextProps.ship);
|
||||
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
|
||||
this.setState({ weaponNames,
|
||||
opponentShields,
|
||||
opponentArmour,
|
||||
maxRange,
|
||||
maxDps,
|
||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range of a ship's weapons
|
||||
* @param {Object} ship The ship
|
||||
* @returns {int} The maximum range, in metres
|
||||
*/
|
||||
_calcMaxRange(ship) {
|
||||
let maxRange = 1000; // Minimum
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const thisRange = ship.hardpoints[i].m.getRange();
|
||||
if (thisRange > maxRange) {
|
||||
maxRange = thisRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum sustained single-weapon DPS for this ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @return {number} The maximum sustained single-weapon DPS
|
||||
*/
|
||||
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
|
||||
// Additional information to allow effectiveness calculations
|
||||
let maxSDps = 0;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
|
||||
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
if (thisSDps > maxSDps) {
|
||||
maxSDps = thisSDps;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxSDps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the weapon names for this ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} context The context
|
||||
* @return {array} The weapon names
|
||||
*/
|
||||
_weaponNames(ship, context) {
|
||||
const translate = context.language.translate;
|
||||
let names = [];
|
||||
let num = 1;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
|
||||
let engineering;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id) {
|
||||
engineering += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
}
|
||||
if (engineering) {
|
||||
name = name + ' (' + engineering + ')';
|
||||
}
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
|
||||
* @param {Object} opponent The target
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
|
||||
* @param {Object} engagementRange The engagement range
|
||||
* @return {array} The array of weapon DPS
|
||||
*/
|
||||
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
|
||||
let results = {};
|
||||
let weaponNum = 0;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
|
||||
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render damage dealt
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { maxRange } = this.state;
|
||||
const { ship, opponent, engagementRange } = this.props;
|
||||
|
||||
const sortOrder = this._sortOrder;
|
||||
const onCollapseExpand = this._onCollapseExpand;
|
||||
|
||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<LineChart
|
||||
xMax={maxRange}
|
||||
yMax={this.state.maxDps}
|
||||
xLabel={translate('range')}
|
||||
xUnit={translate('m')}
|
||||
yLabel={translate('sdps')}
|
||||
series={this.state.weaponNames}
|
||||
xMark={this.props.engagementRange}
|
||||
colors={DAMAGE_DEALT_COLORS}
|
||||
func={this.state.calcSDpsFunc}
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import * as ES from './es';
|
||||
import * as FR from './fr';
|
||||
import * as IT from './it';
|
||||
import * as RU from './ru';
|
||||
import d3 from 'd3';
|
||||
import * as PL from './pl';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
let fallbackTerms = EN.terms;
|
||||
|
||||
@@ -23,47 +24,55 @@ export function getLanguage(langCode) {
|
||||
case 'fr': lang = FR; break;
|
||||
case 'it': lang = IT; break;
|
||||
case 'ru': lang = RU; break;
|
||||
case 'pl': lang = PL; break;
|
||||
default:
|
||||
lang = EN;
|
||||
}
|
||||
|
||||
let currentTerms = lang.terms;
|
||||
let d3Locale = d3.locale(lang.formats);
|
||||
let gen = d3Locale.numberFormat('n');
|
||||
let d3Locale = d3.formatLocale(lang.formats);
|
||||
let gen = d3Locale.format('');
|
||||
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
|
||||
|
||||
if(lang === EN) {
|
||||
translate = (t) => { return currentTerms[t] || t; };
|
||||
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
|
||||
} else {
|
||||
translate = (t) => { return currentTerms[t] || fallbackTerms[t] || t; };
|
||||
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || fallbackTerms[t + '_' + x] || fallbackTerms[t] || t; };
|
||||
}
|
||||
|
||||
return {
|
||||
formats: {
|
||||
gen, // General number format (.e.g 1,001,001.1234)
|
||||
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
|
||||
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
|
||||
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
|
||||
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
|
||||
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
|
||||
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
|
||||
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
|
||||
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
|
||||
gen, // General number format (.e.g 1,001,001.1234)
|
||||
int: d3Locale.format(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
|
||||
f1: d3Locale.format(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
|
||||
f2: d3Locale.format(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
|
||||
s2: d3Locale.format('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
|
||||
pct: d3Locale.format('.2%'), // % to 2 decimal places (.e.g 5.40%)
|
||||
pct1: d3Locale.format('.1%'), // % to 1 decimal places (.e.g 5.4%)
|
||||
r1: d3Locale.format('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
|
||||
r2: d3Locale.format('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
|
||||
rPct: d3Locale.format('.0%'), // % to 0 decimal places (.e.g 5%)
|
||||
round1: (d) => gen(round(d, 1)), // Round to 0-1 decimal places (e.g. 5.1, 4)
|
||||
round: (d) => gen(round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
|
||||
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
|
||||
},
|
||||
translate,
|
||||
units: {
|
||||
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
|
||||
MJ: <u> {translate('MJ')}</u>, // Mega Joules
|
||||
'm/s': <u> {translate('m/s')}</u>, // Meters per second
|
||||
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||
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
|
||||
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
|
||||
ps: <u>{translate('/s')}</u>, // per second
|
||||
pm: <u>{translate('/min')}</u>, // per minute
|
||||
T: <u> {translate('T')}</u>, // Metric Tons
|
||||
s: <u>{translate('secs')}</u>, // Seconds
|
||||
T: <u>{translate('T')}</u>, // Metric Tons
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -78,5 +87,6 @@ export const Languages = {
|
||||
it: 'Italiano',
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
ru: 'ру́сский'
|
||||
ru: 'ру́сский',
|
||||
pl: 'polski'
|
||||
};
|
||||
|
||||
@@ -24,10 +24,76 @@ export const terms = {
|
||||
PHRASE_NO_BUILDS: 'No builds added to comparison!',
|
||||
PHRASE_NO_RETROCH: 'No Retrofitting changes',
|
||||
PHRASE_SELECT_BUILDS: 'Select builds to compare',
|
||||
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
|
||||
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
|
||||
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge, assuming full SYS capacitor to start with',
|
||||
PHRASE_SG_RECOVER: 'Time from 0% to 50% charge, assuming full SYS capacitor to start with',
|
||||
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
|
||||
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
|
||||
PHRASE_ENGAGEMENT_RANGE: 'The distance between your ship and its target',
|
||||
PHRASE_SELECT_BLUEPRINT: 'Click to select a blueprint',
|
||||
PHRASE_BLUEPRINT_WORST: 'Worst primary values for this blueprint',
|
||||
PHRASE_BLUEPRINT_RANDOM: 'Random selection between worst and best primary values for this blueprint',
|
||||
PHRASE_BLUEPRINT_BEST: 'Best primary values for this blueprint',
|
||||
PHRASE_BLUEPRINT_EXTREME: 'Best beneficial and worst detrimental primary values for this blueprint',
|
||||
PHRASE_BLUEPRINT_RESET: 'Remove all modifications and blueprint',
|
||||
PHRASE_SELECT_SPECIAL: 'Click to select an experimental effect',
|
||||
PHRASE_NO_SPECIAL: 'No experimental effect',
|
||||
PHRASE_SHOPPING_LIST: 'Stations that sell this build',
|
||||
PHRASE_REFIT_SHOPPING_LIST: 'Stations that sell required modules',
|
||||
PHRASE_TOTAL_EFFECTIVE_SHIELD: 'Total amount of damage that can be taken from each damage type, if using all shield cells',
|
||||
PHRASE_TIME_TO_LOSE_SHIELDS: 'Shields will hold for',
|
||||
PHRASE_TIME_TO_RECOVER_SHIELDS: 'Shields will recover in',
|
||||
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Shields will recharge in',
|
||||
PHRASE_SHIELD_SOURCES: 'Breakdown of the supply of shield energy',
|
||||
PHRASE_EFFECTIVE_SHIELD: 'Effective shield strength against different damage types',
|
||||
PHRASE_ARMOUR_SOURCES: 'Breakdown of the supply of armour',
|
||||
PHRASE_EFFECTIVE_ARMOUR: 'Effective armour strength against different damage types',
|
||||
PHRASE_DAMAGE_TAKEN: '% of raw damage taken for different damage types',
|
||||
PHRASE_TIME_TO_LOSE_ARMOUR: 'Armour will hold for',
|
||||
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Protection for hardpoints',
|
||||
PHRASE_MODULE_PROTECTION_INTERNAL: 'Protection for all other modules',
|
||||
PHRASE_SHIELD_DAMAGE: 'Breakdown of sources for sustained DPS against shields',
|
||||
PHRASE_ARMOUR_DAMAGE: 'Breakdown of sources for sustained DPS against armour',
|
||||
|
||||
PHRASE_TIME_TO_REMOVE_SHIELDS: 'Will remove shields in',
|
||||
TT_TIME_TO_REMOVE_SHIELDS: 'With sustained fire by all weapons',
|
||||
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Will remove armour in',
|
||||
TT_TIME_TO_REMOVE_ARMOUR: 'With sustained fire by all weapons',
|
||||
PHRASE_TIME_TO_DRAIN_WEP: 'Will drain WEP in',
|
||||
TT_TIME_TO_DRAIN_WEP: 'Time to drain WEP capacitor with all weapons firing',
|
||||
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
|
||||
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
|
||||
TT_MODULE_ARMOUR: 'Armour protecting against module damage',
|
||||
TT_MODULE_PROTECTION_EXTERNAL: 'Percentage of damage diverted from hardpoints to module reinforcement packages',
|
||||
TT_MODULE_PROTECTION_INTERNAL: 'Percentage of damage diverted from non-hardpoint modules to module reinforcement packages',
|
||||
|
||||
TT_EFFECTIVE_SDPS_SHIELDS: 'Actual sustained DPS whilst WEP capacitor is not empty',
|
||||
TT_EFFECTIVENESS_SHIELDS: 'Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m',
|
||||
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
|
||||
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
|
||||
|
||||
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields',
|
||||
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour',
|
||||
|
||||
TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
|
||||
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass',
|
||||
TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
|
||||
TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Power distributor not able to supply enough power to boost',
|
||||
TT_SUMMARY_SHIELDS: 'Raw shield strength, including boosters',
|
||||
TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'No shield generator or shield generator powered off',
|
||||
TT_SUMMARY_INTEGRITY: 'Ship integrity, including bulkheads and hull reinforcement packages',
|
||||
TT_SUMMARY_HULL_MASS: 'Mass of the hull prior to any modules being installed',
|
||||
TT_SUMMARY_UNLADEN_MASS: 'Mass of the hull and modules prior to any fuel or cargo',
|
||||
TT_SUMMARY_LADEN_MASS: 'Mass of the hull and modules with full fuel and cargo',
|
||||
TT_SUMMARY_DPS: 'Damage per second with all weapons firing',
|
||||
TT_SUMMARY_EPS: 'WEP capacitor consumed per second with all weapons firing',
|
||||
TT_SUMMARY_TTD: 'Time to drain WEP capacitor with all weapons firing and 4 pips to WEP',
|
||||
TT_SUMMARY_MAX_SINGLE_JUMP: 'Farthest possible jump range with no cargo and only enough fuel for the jump itself',
|
||||
TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Farthest possible jump range with no cargo and a full fuel tank',
|
||||
TT_SUMMARY_LADEN_SINGLE_JUMP: 'Farthest possible jump range with full cargo and a full fuel tank',
|
||||
TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time',
|
||||
TT_SUMMARY_LADEN_TOTAL_JUMP: 'Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time',
|
||||
|
||||
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
|
||||
|
||||
// Other languages fallback to these values
|
||||
// Only Translate to other languages if the name is different in-game
|
||||
@@ -37,11 +103,13 @@ export const terms = {
|
||||
bsg: 'Bi-Weave Shield Generator',
|
||||
c: 'Cannon',
|
||||
cc: 'Collector Limpet Controller',
|
||||
cm: 'Countermeasure',
|
||||
ch: 'Chaff Launcher',
|
||||
cr: 'Cargo Rack',
|
||||
cs: 'Cargo Scanner',
|
||||
cs: 'Manifest Scanner',
|
||||
dc: 'Docking Computer',
|
||||
ec: 'Electronic Countermeasure',
|
||||
fc: 'Fragment Cannon',
|
||||
fh: 'Fighter Hangar',
|
||||
fi: 'FSD Interdictor',
|
||||
fs: 'Fuel Scoop',
|
||||
fsd: 'Frame Shift Drive',
|
||||
@@ -49,17 +117,24 @@ export const terms = {
|
||||
fx: 'Fuel Transfer Limpet Controller',
|
||||
hb: 'Hatch Breaker Limpet Controller',
|
||||
hr: 'Hull Reinforcement Package',
|
||||
hs: 'Heat Sink Launcher',
|
||||
kw: 'Kill Warrant Scanner',
|
||||
ls: 'Life Support',
|
||||
mc: 'Multi-cannon',
|
||||
ml: 'Mining Laser',
|
||||
mr: 'Missile Rack',
|
||||
mrp: 'Module Reinforcement Package',
|
||||
nl: 'Mine Launcher',
|
||||
pa: 'Plasma Accelerator',
|
||||
pas: 'Planetary Approach Suite',
|
||||
pc: 'Prospector Limpet Controller',
|
||||
pce: 'Economy Class Passenger Cabin',
|
||||
pci: 'Business Class Passenger Cabin',
|
||||
pcm: 'First Class Passenger Cabin',
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
pd: 'power distributor',
|
||||
pl: 'Pulse Laser',
|
||||
po: 'Point Defence',
|
||||
pp: 'Power Plant',
|
||||
psg: 'Prismatic Shield Generator',
|
||||
pv: 'Planetary Vehicle Hangar',
|
||||
@@ -67,11 +142,329 @@ export const terms = {
|
||||
rg: 'Rail Gun',
|
||||
s: 'Sensors',
|
||||
sb: 'Shield Booster',
|
||||
sc: 'Scanner',
|
||||
sc: 'Stellar Scanners',
|
||||
scb: 'Shield Cell Bank',
|
||||
sg: 'Shield Generator',
|
||||
ss: 'Surface Scanners',
|
||||
t: 'thrusters',
|
||||
tp: 'Torpedo Pylon',
|
||||
ul: 'Burst Laser',
|
||||
ws: 'Frame Shift Wake Scanner'
|
||||
ws: 'Frame Shift Wake Scanner',
|
||||
|
||||
// Items on the outfitting page
|
||||
// Notification of restricted slot
|
||||
emptyrestricted: 'empty (restricted)',
|
||||
'damage dealt to': 'Damage dealt to',
|
||||
'damage received from': 'Damage received from',
|
||||
'against shields': 'Against shields',
|
||||
'against hull': 'Against hull',
|
||||
'total effective shield': 'Total effective shield',
|
||||
|
||||
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
|
||||
ammunition: 'Ammo',
|
||||
|
||||
// Unit for seconds
|
||||
secs: 's',
|
||||
|
||||
rebuildsperbay: 'Rebuilds per bay',
|
||||
|
||||
// Blueprint rolls
|
||||
worst: 'Worst',
|
||||
average: 'Average',
|
||||
random: 'Random',
|
||||
best: 'Best',
|
||||
extreme: 'Extreme',
|
||||
reset: 'Reset',
|
||||
|
||||
// Weapon, offence, defence and movement
|
||||
dpe: 'Damage per MJ of energy',
|
||||
dps: 'Damage per second',
|
||||
sdps: 'Sustained damage per second',
|
||||
dpssdps: 'Damage per second (sustained damage per second)',
|
||||
eps: 'Energy per second',
|
||||
epsseps: 'Energy per second (sustained energy per second)',
|
||||
hps: 'Heat per second',
|
||||
hpsshps: 'Heat per second (sustained heat per second)',
|
||||
'damage by': 'Damage by',
|
||||
'damage from': 'Damage from',
|
||||
'shield cells': 'Shield cells',
|
||||
'recovery': 'Recovery',
|
||||
'recharge': 'Recharge',
|
||||
'engine pips': 'Engine Pips',
|
||||
'4b': '4 pips and boost',
|
||||
'speed': 'Speed',
|
||||
'pitch': 'Pitch',
|
||||
'roll': 'Roll',
|
||||
'yaw': 'Yaw',
|
||||
'internal protection': 'Internal protection',
|
||||
'external protection': 'External protection',
|
||||
'engagement range': 'Engagement range',
|
||||
'total': 'Total',
|
||||
|
||||
// Modifications
|
||||
ammo: 'Ammunition maximum',
|
||||
boot: 'Boot time',
|
||||
brokenregen: 'Broken regeneration rate',
|
||||
burst: 'Burst',
|
||||
burstrof: 'Burst rate of fire',
|
||||
clip: 'Ammunition clip',
|
||||
damage: 'Damage',
|
||||
distdraw: 'Distributor draw',
|
||||
duration: 'Duration',
|
||||
eff: 'Efficiency',
|
||||
engcap: 'Engines capacity',
|
||||
engrate: 'Engines recharge rate',
|
||||
explres: 'Explosive resistance',
|
||||
facinglimit: 'Facing limit',
|
||||
hullboost: 'Hull boost',
|
||||
hullreinforcement: 'Hull reinforcement',
|
||||
integrity: 'Integrity',
|
||||
jitter: 'Jitter',
|
||||
kinres: 'Kinetic resistance',
|
||||
maxfuel: 'Maximum fuel per jump',
|
||||
mass: 'Mass',
|
||||
optmass: 'Optimal mass',
|
||||
optmul: 'Optimal multiplier',
|
||||
pgen: 'Power generation',
|
||||
piercing: 'Piercing',
|
||||
power: 'Power draw',
|
||||
protection: 'Protection',
|
||||
range: 'Range',
|
||||
ranget: 'Range', // Range in time (for FSD interdictor)
|
||||
regen: 'Regeneration rate',
|
||||
reload: 'Reload',
|
||||
rof: 'Rate of fire',
|
||||
angle: 'Scan angle',
|
||||
scanrate: 'Scan rate',
|
||||
scantime: 'Scan time',
|
||||
shield: 'Shield',
|
||||
shieldboost: 'Shield boost',
|
||||
shieldreinforcement: 'Shield reinforcement',
|
||||
shotspeed: 'Shot speed',
|
||||
spinup: 'Spin up time',
|
||||
syscap: 'Systems capacity',
|
||||
sysrate: 'Systems recharge rate',
|
||||
thermload: 'Thermal load',
|
||||
thermres: 'Thermal resistance',
|
||||
wepcap: 'Weapons capacity',
|
||||
weprate: 'Weapons recharge rate',
|
||||
|
||||
// Shield generators use a different terminology
|
||||
minmass_sg: 'Minimum hull mass',
|
||||
optmass_sg: 'Optimal hull mass',
|
||||
maxmass_sg: 'Maximum hull mass',
|
||||
minmul_sg: 'Minimum strength',
|
||||
optmul_sg: 'Optimal strength',
|
||||
maxmul_sg: 'Minimum strength',
|
||||
minmass_psg: 'Minimum hull mass',
|
||||
optmass_psg: 'Optimal hull mass',
|
||||
maxmass_psg: 'Maximum hull mass',
|
||||
minmul_psg: 'Minimum strength',
|
||||
optmul_psg: 'Optimal strength',
|
||||
maxmul_psg: 'Minimum strength',
|
||||
minmass_bsg: 'Minimum hull mass',
|
||||
optmass_bsg: 'Optimal hull mass',
|
||||
maxmass_bsg: 'Maximum hull mass',
|
||||
minmul_bsg: 'Minimum strength',
|
||||
optmul_bsg: 'Optimal strength',
|
||||
maxmul_bsg: 'Minimum strength',
|
||||
|
||||
range_s: 'Typical emission range',
|
||||
|
||||
// Damage types
|
||||
absolute: 'Absolute',
|
||||
explosive: 'Explosive',
|
||||
kinetic: 'Kinetic',
|
||||
thermal: 'Thermal',
|
||||
|
||||
// Shield sources
|
||||
generator: 'Generator',
|
||||
boosters: 'Boosters',
|
||||
cells: 'Cells',
|
||||
|
||||
// Armour sources
|
||||
bulkheads: 'Bulkheads',
|
||||
reinforcement: 'Reinforcement',
|
||||
|
||||
// Help text
|
||||
HELP_TEXT: `
|
||||
<h1>Introduction</h1>
|
||||
Coriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis.
|
||||
|
||||
<h1>Importing Your Ship Into Coriolis</h1>
|
||||
Often, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well. </p>
|
||||
|
||||
<h2>Importing Your Ship From EDDI</h2>
|
||||
To import your ship from EDDI first ensure that your connection to the Frontier servers' companion API is working. To do this check the 'Companion App' tab where you should see "Your connection to the companion app is operational". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.</p>
|
||||
|
||||
Once you have a working companion API connection go to the 'Shipyard' tab. At the right-hand side of each ship is an 'Export to Coriolis' button that will open your default web browser in Coriolis with the ship's build. </p>
|
||||
|
||||
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
|
||||
|
||||
Also, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of "5" and to be disabled by default. You can change this after import in the Power Management section. </p>
|
||||
|
||||
<h2>Importing Your Ship From EDMC</h2>
|
||||
To import your ship from EDMC once your connection to the Frontier servers' companion API is working go to 'Settings ->Configuration' and set the 'Preferred Shipyard' to 'Coriolis'. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship's build.</p>
|
||||
|
||||
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
|
||||
|
||||
<h1>Understanding And Using The Outfitting Panels</h1>
|
||||
The outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below. </p>
|
||||
|
||||
<h2>Key Values</h2>
|
||||
Along the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels. </p>
|
||||
|
||||
Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.</p>
|
||||
|
||||
All values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.</p>
|
||||
|
||||
<h2>Modules</h2>
|
||||
The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions). </p>
|
||||
|
||||
To add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown. </p>
|
||||
|
||||
To remove a module from a slot right-click on the module. </p>
|
||||
|
||||
To move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the 'Alt' key. </p>
|
||||
|
||||
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers). </p>
|
||||
|
||||
<h2>Ship Controls</h2>
|
||||
The ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have. </p>
|
||||
|
||||
Ship control settings are saved as part of a build. </p>
|
||||
|
||||
<h2>Opponent</h2>
|
||||
The opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels. </p>
|
||||
|
||||
Opponent settings are saved as part of a build. </p>
|
||||
|
||||
<h2>Power and Costs Sub-panels</h2>
|
||||
<h3>Power</h3>
|
||||
The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module. Disabled modules will not be included in the build's statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required. </p>
|
||||
|
||||
<h3>Costs</h3>
|
||||
The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the 'Settings' at the top-right of the page.</p>
|
||||
|
||||
The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.</p>
|
||||
|
||||
The reload costs provides information about the costs of reloading your current build.</p>
|
||||
|
||||
<h2>Profiles</h2>
|
||||
Profiles provide graphs that show the general performance of modules in your build
|
||||
|
||||
<h3>Engine Profile</h3>
|
||||
The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button. </p>
|
||||
|
||||
<h3>FSD Profile</h3>
|
||||
The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build, </p>
|
||||
|
||||
<h3>Movement Profile</h3>
|
||||
The movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:
|
||||
|
||||
<dl>
|
||||
<dt>Speed</dt><dd>The fastest the ship can move, in metres per second</dd>
|
||||
<dt>Pitch</dt><dd>The fastest the ship can raise or lower its nose, in degrees per second</dd>
|
||||
<dt>Roll</dt><dd>The fastest the ship can roll its body, in degrees per second</dd>
|
||||
<dt>Yaw</dt><dd>The fastest the ship can turn its nose left or right, in degrees per second</dd>
|
||||
</dl>
|
||||
|
||||
Your movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button. </p>
|
||||
|
||||
<h3>Damage Profile</h3>
|
||||
The damage profile provides two graphs showing how the the build's damage to the opponent's shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build's weapons with the opponent's shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent. </p>
|
||||
|
||||
<h2>Offence</h2>
|
||||
<h3>Summary</h3>
|
||||
The offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together. </p>
|
||||
|
||||
<h3>Offence Metrics</h3>
|
||||
The offence metrics panel provides information about your offence. </p>
|
||||
|
||||
Time to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain. </p>
|
||||
|
||||
The next value is the time it will take you to remove your opponent's shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor. </p>
|
||||
|
||||
The next value is the time it will take you to remove your opponent's armour. This follows the same logic as the time to remove shields. </p>
|
||||
|
||||
<h3>Shield Damage Sources</h3>
|
||||
The shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
|
||||
|
||||
<h3>Hull Damage Sources</h3>
|
||||
The hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
|
||||
|
||||
<h2>Defence</h2>
|
||||
<h3>Shield Metrics</h3>
|
||||
The shield metrics provides information about your shield defence. </p>
|
||||
|
||||
Raw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values. </p>
|
||||
|
||||
The time the shields will hold for is the time it will take your opponent' to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost. </p>
|
||||
|
||||
The time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor. </p>
|
||||
|
||||
The time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor. </p>
|
||||
|
||||
</h3>Shield Sources</h3>
|
||||
This chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided. </p>
|
||||
|
||||
</h3>Damage Taken</h3>
|
||||
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
|
||||
|
||||
</h3>Effective Shield</h3>
|
||||
This graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type. </p>
|
||||
|
||||
<h3>Amour Metrics</h3>
|
||||
The armour metrics provides information about your armour defence. </p>
|
||||
|
||||
Raw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values. </p>
|
||||
|
||||
The time the armour will hold for is the time it will take your opponent' to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules. </p>
|
||||
|
||||
Raw module armour is the sum of the protection from your module reinforcement packages. </p>
|
||||
|
||||
Protection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages. </p>
|
||||
|
||||
Protection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages. </p>
|
||||
|
||||
</h3>Armour Sources</h3>
|
||||
This chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided. </p>
|
||||
|
||||
</h3>Damage Taken</h3>
|
||||
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
|
||||
|
||||
</h3>Effective Armour</h3>
|
||||
This graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type. </p>
|
||||
|
||||
<h1>Keyboard Shortcuts</h1>
|
||||
<dl>
|
||||
<dt>Ctrl-b</dt><dd>toggle boost</dd>
|
||||
<dt>Ctrl-e</dt><dd>open export dialogue (outfitting page only)</dd>
|
||||
<dt>Ctrl-h</dt><dd>open help dialogue</dd>
|
||||
<dt>Ctrl-i</dt><dd>open import dialogue</dd>
|
||||
<dt>Ctrl-o</dt><dd>open shortlink dialogue</dd>
|
||||
<dt>Ctrl-left-arrow</dt><dd>increase SYS capacitor</dd>
|
||||
<dt>Ctrl-up-arrow</dt><dd>increase ENG capacitor</dd>
|
||||
<dt>Ctrl-right-arrow</dt><dd>increase WEP capacitor</dd>
|
||||
<dt>Ctrl-down-arrow</dt><dd>reset power distributor</dd>
|
||||
<dt>Esc</dt><dd>close any open dialogue</dd>
|
||||
</dl>
|
||||
<h1>Glossary</h1>
|
||||
<dl>
|
||||
<dt>Absolute damage</dt><dd>A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances</dd>
|
||||
<dt>DPS</dt><dd>Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions</dd>
|
||||
<dt>EPS</dt><dd>Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing</dd>
|
||||
<dt>HPS</dt><dd>Heat per second; the amount of heat that a weapon or a ship generates per second when firing</dd>
|
||||
<dt>Effectivness</dt><dd>A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target's hull and shield resistances, and the target's hardness</dd>
|
||||
<dt>Explosive damage</dt><dd>A type of damage, protected against by explosive resistance</dd>
|
||||
<dt>Hardness</dt><dd>The inherent resistance to damage of a ship's hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship's hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness</dd>
|
||||
<dt>Falloff</dt><dd>The distance at which a weapons starts to do less damage than its stated DPS</dd>
|
||||
<dt>Kinetic damage</dt><dd>A type of damage, protected against by kinetic resistance</dd>
|
||||
<dt>SDPS</dt><dd>Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload</dd>
|
||||
<dt>SEPS</dt><dd>Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload</dd>
|
||||
<dt>SHPS</dt><dd>Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload</dd>
|
||||
<dt>Thermal damage</dt><dd>A type of damage, protected against by thermal resistance</dd>
|
||||
</dl>
|
||||
|
||||
`,
|
||||
};
|
||||
|
||||
77
src/app/i18n/pl.js
Normal file
77
src/app/i18n/pl.js
Normal file
@@ -0,0 +1,77 @@
|
||||
export const formats = {
|
||||
decimal: '.',
|
||||
thousands: ',',
|
||||
grouping: [3],
|
||||
currency: ['$', ''],
|
||||
dateTime: '%a %b %e %X %Y',
|
||||
date: '%m/%d/%Y',
|
||||
time: '%H:%M:%S',
|
||||
periods: ['AM', 'PM'],
|
||||
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
|
||||
shortDays: ['Nie', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'],
|
||||
months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
|
||||
shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru']
|
||||
};
|
||||
|
||||
export const terms = {
|
||||
PHRASE_ALT_ALL: 'Alt + kliknięcie by wypełnić wszystkie sloty',
|
||||
PHRASE_BACKUP_DESC: 'Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę',
|
||||
PHRASE_CONFIRMATION: 'Czy jesteś pewien?',
|
||||
PHRASE_EXPORT_DESC: 'Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach',
|
||||
PHRASE_FASTEST_RANGE: 'Maksymalna ilość skoków na najwyższym zasięgu',
|
||||
PHRASE_IMPORT: 'Wklej tu JSON lub importuj',
|
||||
PHRASE_LADEN: 'Masa statku + paliwo + ładunek',
|
||||
PHRASE_NO_BUILDS: 'Nie dodano schematu do porównania!',
|
||||
PHRASE_NO_RETROCH: 'Brak zmian retrofit',
|
||||
PHRASE_SELECT_BUILDS: 'Wybierz schematy do porównania',
|
||||
PHRASE_SG_RECHARGE: 'Czas od 50% do 100% naładowania',
|
||||
PHRASE_SG_RECOVER: 'Odnowienie (do 50%) po upadku',
|
||||
PHRASE_UNLADEN: 'Masa statku z wyłączeniem paliwa i ładunku',
|
||||
PHRASE_UPDATE_RDY: 'Dostępna aktualizacja! Naciśnij by odświeżyć',
|
||||
|
||||
// Other languages fallback to these values
|
||||
// Only Translate to other languages if the name is different in-game
|
||||
am: 'Auto Field-Maintenance Unit',
|
||||
bh: 'Bulkheads',
|
||||
bl: 'Beam Laser',
|
||||
bsg: 'Bi-Weave Shield Generator',
|
||||
c: 'Cannon',
|
||||
cc: 'Collector Limpet Controller',
|
||||
cm: 'Countermeasure',
|
||||
cr: 'Cargo Rack',
|
||||
cs: 'Cargo Scanner',
|
||||
dc: 'Docking Computer',
|
||||
fc: 'Fragment Cannon',
|
||||
fi: 'FSD Interdictor',
|
||||
fs: 'Fuel Scoop',
|
||||
fsd: 'Frame Shift Drive',
|
||||
ft: 'Fuel Tank',
|
||||
fx: 'Fuel Transfer Limpet Controller',
|
||||
hb: 'Hatch Breaker Limpet Controller',
|
||||
hr: 'Hull Reinforcement Package',
|
||||
kw: 'Kill Warrant Scanner',
|
||||
ls: 'Life Support',
|
||||
mc: 'Multi-cannon',
|
||||
ml: 'Mining Laser',
|
||||
mr: 'Missile Rack',
|
||||
nl: 'Mine Launcher',
|
||||
pa: 'Plasma Accelerator',
|
||||
pas: 'Planetary Approach Suite',
|
||||
pc: 'Prospector Limpet Controller',
|
||||
pd: 'power distributor',
|
||||
pl: 'Pulse Laser',
|
||||
pp: 'Power Plant',
|
||||
psg: 'Prismatic Shield Generator',
|
||||
pv: 'Planetary Vehicle Hangar',
|
||||
rf: 'Refinery',
|
||||
rg: 'Rail Gun',
|
||||
s: 'Sensors',
|
||||
sb: 'Shield Booster',
|
||||
sc: 'Scanner',
|
||||
scb: 'Shield Cell Bank',
|
||||
sg: 'Shield Generator',
|
||||
t: 'thrusters',
|
||||
tp: 'Torpedo Pylon',
|
||||
ul: 'Burst Laser',
|
||||
ws: 'Frame Shift Wake Scanner'
|
||||
};
|
||||
@@ -18,15 +18,15 @@ export const terms = {
|
||||
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
|
||||
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
|
||||
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
|
||||
PHRASE_FASTEST_RANGE: 'Последовательные максимальные дальности прыжков', // Consecutive max range jumps
|
||||
PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
|
||||
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
|
||||
PHRASE_LADEN: 'Корабль Масса + топлива + Грузы', // Ship Mass + Fuel + Cargo
|
||||
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
|
||||
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
|
||||
PHRASE_NO_RETROCH: 'нет ранних версий сборкиконфигурации', // No Retrofitting changes
|
||||
PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
|
||||
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
|
||||
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
|
||||
PHRASE_SG_RECOVER: 'восстановление [до 60%] после падения\сбития', // Recovery (to 50%) after collapse
|
||||
PHRASE_UNLADEN: 'Судно Масса без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
|
||||
PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
|
||||
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
|
||||
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
|
||||
|
||||
// Units / Metrics
|
||||
@@ -117,7 +117,7 @@ export const terms = {
|
||||
jumps: 'Прыжков',
|
||||
laden: 'Груженый',
|
||||
language: 'Язык',
|
||||
maneuverability: 'Моневренность',
|
||||
maneuverability: 'Маневренность',
|
||||
|
||||
mass: 'Масса',
|
||||
max: 'Макс',
|
||||
|
||||
@@ -24,30 +24,23 @@ export default class AboutPage extends Page {
|
||||
*/
|
||||
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</span></h1>
|
||||
<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>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.</p>
|
||||
|
||||
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/cmmcleod/coriolis' target='_blank' title='Coriolis Github Project'>
|
||||
<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/cmmcleod/coriolis
|
||||
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>
|
||||
|
||||
<form action='https://www.paypal.com/cgi-bin/webscr' method='post' target='_blank'>
|
||||
<input type='hidden' name='cmd' value='_s-xclick' />
|
||||
<input type='hidden' name='encrypted' value='-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCjl3XoqQ3Q+x/qS7Va1lwvF0IgUs8gBbrwj1/uEv/xFyPSB2G0kgWqiB2c/8vvfcjjyMr4nlzLUlmQ0yl1zZaeTXFciN5a+JsvaBISThIlN9UP7PXP61TVHCECtt/hBNtlOmg8/gG8khJCj8+qi81XsNAz5bEDpdahKW3fwGHD4jELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI4EVkn3RE+9qAgYhg2sTmY1Gul2yJyLYJZPRMO/PwgzEogb2NlIcshJSO+KvBea5NjjTXN2EJNqJa24h4lGA1mdrSgzTGDrVbdcnuti9+7ggn5R5s5IwEEQnN4JQx3IAqsp3UmJbti5t776Ns50nQbjA8NzxI+gwUmIvUQaVs6wC4HYXG6q8QtqUIWeVDhvbnt+H8oIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUwNjA1MjEwNDQzWjAjBgkqhkiG9w0BCQQxFgQUx6Bs50H7tbYJln13pP5J7J1KiSUwDQYJKoZIhvcNAQEBBQAEgYBiOr1RX38uvwghuIZxKpjXX4LG/GoyYM6citfsBD5vjUGj0udmsamjlur+dwxJNs9dULnJO6huoTvxqxTui0Mh3n21YKoMqVE/erfNk2XygrJw9bEtW+HXjU3F+OGKR7dfD9STp2ZlvTEvZR9JRV5A/udC9/9U9eD5iLKRRwkIBg==-----END PKCS7-----' />
|
||||
<input type='image' src='https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif' name='submit' alt='PayPal - Donate to Coriolis.io' style={{ border:'none' }} />
|
||||
<img alt='' border='0' src='https://www.paypalobjects.com/en_US/i/scr/pixel.gif' width='1' height='1' />
|
||||
</form>
|
||||
|
||||
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
|
||||
|
||||
<h3>Chat</h3>
|
||||
<p>You can chat to us on <a href='https://www.hipchat.com/gfYQiZcmy' target='_blank'>HipChat</a> and sign up to chat on all E:D HipChat groups <a href='https://elite-dangerous.hipchat.com/invite/74670/a3af7ea57008362d83d05fada09bdf84' target='_blank'>here</a>.</p>
|
||||
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export default class ComparisonPage extends Page {
|
||||
* @return {Object} New state object
|
||||
*/
|
||||
_initState(context) {
|
||||
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
|
||||
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
|
||||
let params = context.route.params;
|
||||
let code = params.code;
|
||||
let name = params.name ? decodeURIComponent(params.name) : null;
|
||||
@@ -131,7 +131,7 @@ export default class ComparisonPage extends Page {
|
||||
builds.sort(sortBy(predicate));
|
||||
|
||||
return {
|
||||
title: 'Coriolis - Compare',
|
||||
title: 'Coriolis EDCD Edition - Compare',
|
||||
predicate,
|
||||
desc,
|
||||
facets,
|
||||
@@ -345,7 +345,7 @@ 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}`;
|
||||
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component {
|
||||
if (ed) {
|
||||
content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
|
||||
<div className='cen'>
|
||||
<a href='https://github.com/cmmcleod/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
|
||||
<a href='https://github.com/edcd/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
|
||||
{' if this keeps happening. Add these details:'}
|
||||
</div>
|
||||
<div style={{ marginTop: '2em' }}>
|
||||
@@ -39,9 +39,16 @@ export default class ErrorDetails extends React.Component {
|
||||
</div>;
|
||||
}
|
||||
|
||||
const importerror = ed && ed.scriptUrl && ed.scriptUrl.indexOf('/import') != -1;
|
||||
|
||||
return <div className='error'>
|
||||
<h1>Jameson, we have a problem..</h1>
|
||||
<h1><small>{error.message}</small></h1>
|
||||
<br/>
|
||||
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
|
||||
<br/>
|
||||
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
|
||||
<br/>
|
||||
{content}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,41 @@
|
||||
import React from 'react';
|
||||
// import Perf from 'react-addons-perf';
|
||||
import { findDOMNode } from 'react-dom';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import cn from 'classnames';
|
||||
import Page from './Page';
|
||||
import Router from '../Router';
|
||||
import Persist from '../stores/Persist';
|
||||
import * as Utils from '../utils/UtilityFunctions';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { toDetailedBuild } from '../shipyard/Serializer';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
|
||||
import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons';
|
||||
import LZString from 'lz-string';
|
||||
import ShipSummaryTable from '../components/ShipSummaryTable';
|
||||
import StandardSlotSection from '../components/StandardSlotSection';
|
||||
import HardpointsSlotSection from '../components/HardpointsSlotSection';
|
||||
import HardpointSlotSection from '../components/HardpointSlotSection';
|
||||
import InternalSlotSection from '../components/InternalSlotSection';
|
||||
import UtilitySlotSection from '../components/UtilitySlotSection';
|
||||
import LineChart from '../components/LineChart';
|
||||
import PowerManagement from '../components/PowerManagement';
|
||||
import CostSection from '../components/CostSection';
|
||||
import Pips from '../components/Pips';
|
||||
import Boost from '../components/Boost';
|
||||
import Fuel from '../components/Fuel';
|
||||
import Cargo from '../components/Cargo';
|
||||
import ShipPicker from '../components/ShipPicker';
|
||||
import EngagementRange from '../components/EngagementRange';
|
||||
import OutfittingSubpages from '../components/OutfittingSubpages';
|
||||
import ModalExport from '../components/ModalExport';
|
||||
import Slider from '../components/Slider';
|
||||
import ModalPermalink from '../components/ModalPermalink';
|
||||
|
||||
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
|
||||
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
|
||||
/**
|
||||
* Document Title Generator
|
||||
* @param {String} shipName Ship Name
|
||||
* @param {String} buildName Build Name
|
||||
* @return {String} Document title
|
||||
*/
|
||||
function getTitle(shipName, buildName) {
|
||||
return buildName ? buildName : shipName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Outfitting Page
|
||||
@@ -36,15 +49,25 @@ export default class OutfittingPage extends Page {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = this._initState(context);
|
||||
// window.Perf = Perf;
|
||||
this.state = this._initState(props, context);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._exportBuild = this._exportBuild.bind(this);
|
||||
this._pipsUpdated = this._pipsUpdated.bind(this);
|
||||
this._boostUpdated = this._boostUpdated.bind(this);
|
||||
this._cargoUpdated = this._cargoUpdated.bind(this);
|
||||
this._fuelUpdated = this._fuelUpdated.bind(this);
|
||||
this._opponentUpdated = this._opponentUpdated.bind(this);
|
||||
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* [Re]Create initial state from context
|
||||
* @param {Object} props React component properties
|
||||
* @param {context} context React component context
|
||||
* @return {Object} New state object
|
||||
*/
|
||||
_initState(context) {
|
||||
_initState(props, context) {
|
||||
let params = context.route.params;
|
||||
let shipId = params.ship;
|
||||
let code = params.code;
|
||||
@@ -64,11 +87,13 @@ export default class OutfittingPage extends Page {
|
||||
ship.buildWith(data.defaults); // Populate with default components
|
||||
}
|
||||
|
||||
let fuelCapacity = ship.fuelCapacity;
|
||||
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);
|
||||
return {
|
||||
error: null,
|
||||
title: 'Outfitting - ' + data.properties.name,
|
||||
title: this._getTitle(buildName),
|
||||
costTab: Persist.getCostTab() || 'costs',
|
||||
buildName,
|
||||
newBuildName: buildName,
|
||||
@@ -76,11 +101,18 @@ export default class OutfittingPage extends Page {
|
||||
ship,
|
||||
code,
|
||||
savedCode,
|
||||
fuelCapacity,
|
||||
fuelLevel: 1,
|
||||
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuelCapacity),
|
||||
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuelCapacity),
|
||||
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuelCapacity)
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
opponentSys,
|
||||
opponentEng,
|
||||
opponentWep,
|
||||
engagementRange
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,35 +134,213 @@ export default class OutfittingPage extends Page {
|
||||
this.setState(stateChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the control part of the route
|
||||
*/
|
||||
_updateRouteOnControlChange() {
|
||||
const { ship, shipId, buildName } = this.state;
|
||||
const code = this._fullCode(ship);
|
||||
this._updateRoute(shipId, buildName, code);
|
||||
this.setState({ code });
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a full code for this ship, including any additions due to the outfitting page
|
||||
* @param {Object} ship the ship
|
||||
* @param {number} fuel the fuel carried by the ship (if different from that in state)
|
||||
* @param {number} cargo the cargo carried by the ship (if different from that in state)
|
||||
* @returns {string} the code for this ship
|
||||
*/
|
||||
_fullCode(ship, fuel, cargo) {
|
||||
return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the control information from the build code
|
||||
* @param {Object} ship The ship
|
||||
* @param {string} code The build code
|
||||
* @returns {Object} The control information
|
||||
*/
|
||||
_obtainControlFromCode(ship, code) {
|
||||
// Defaults
|
||||
let sys = 2;
|
||||
let eng = 2;
|
||||
let wep = 2;
|
||||
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 opponentSys = 2;
|
||||
let opponentEng = 2;
|
||||
let opponentWep = 2;
|
||||
let opponentBuild;
|
||||
let engagementRange = 1000;
|
||||
|
||||
// Obtain updates from code, if available
|
||||
if (code) {
|
||||
const parts = code.split('.');
|
||||
if (parts.length >= 5) {
|
||||
// We have control information in the code
|
||||
const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
|
||||
sys = parseFloat(control[0]);
|
||||
eng = parseFloat(control[1]);
|
||||
wep = parseFloat(control[2]);
|
||||
boost = control[3] == 1 ? true : false;
|
||||
fuel = parseFloat(control[4]);
|
||||
cargo = parseInt(control[5]);
|
||||
if (control[6]) {
|
||||
const shipId = control[6];
|
||||
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]);
|
||||
opponent.buildFrom(opponentCode);
|
||||
opponentBuild = control[7];
|
||||
if (opponentBuild) {
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Ship is a stock build
|
||||
opponent.buildWith(Ships[shipId].defaults);
|
||||
}
|
||||
}
|
||||
engagementRange = parseInt(control[8]);
|
||||
}
|
||||
}
|
||||
|
||||
return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when pips have been updated
|
||||
* @param {number} sys SYS pips
|
||||
* @param {number} eng ENG pips
|
||||
* @param {number} wep WEP pips
|
||||
*/
|
||||
_pipsUpdated(sys, eng, wep) {
|
||||
this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when boost has been updated
|
||||
* @param {boolean} boost true if boosting
|
||||
*/
|
||||
_boostUpdated(boost) {
|
||||
this.setState({ boost }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when fuel has been updated
|
||||
* @param {number} fuel the amount of fuel, in T
|
||||
*/
|
||||
_fuelUpdated(fuel) {
|
||||
this.setState({ fuel }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when cargo has been updated
|
||||
* @param {number} cargo the amount of cargo, in T
|
||||
*/
|
||||
_cargoUpdated(cargo) {
|
||||
this.setState({ cargo }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when engagement range has been updated
|
||||
* @param {number} engagementRange the engagement range, in m
|
||||
*/
|
||||
_engagementRangeUpdated(engagementRange) {
|
||||
this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when target ship has been updated
|
||||
* @param {string} opponent the opponent's ship model
|
||||
* @param {string} opponentBuild the name of the opponent's build
|
||||
*/
|
||||
_opponentUpdated(opponent, opponentBuild) {
|
||||
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;
|
||||
if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) {
|
||||
// Ship is a particular build
|
||||
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
|
||||
// Set pips for opponent
|
||||
const opponentParts = Persist.getBuild(opponent, opponentBuild).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]);
|
||||
}
|
||||
} else {
|
||||
// Ship is a stock build
|
||||
opponentShip.buildWith(Ships[opponent].defaults);
|
||||
opponentSys = 2;
|
||||
opponentEng = 2;
|
||||
opponentWep = 2;
|
||||
}
|
||||
|
||||
this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the control code for this outfitting page
|
||||
* @param {number} fuel the fuel carried by the ship (if different from that in state)
|
||||
* @param {number} cargo the cargo carried by the ship (if different from that in state)
|
||||
* @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}`;
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current build
|
||||
*/
|
||||
_saveBuild() {
|
||||
let code = this.state.ship.toString();
|
||||
let { buildName, newBuildName, shipId } = this.state;
|
||||
const { code, buildName, newBuildName, shipId } = this.state;
|
||||
|
||||
if (buildName === newBuildName) {
|
||||
Persist.saveBuild(shipId, buildName, code);
|
||||
this._updateRoute(shipId, buildName, code);
|
||||
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) {
|
||||
// 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);
|
||||
opponentSys = this.state.sys;
|
||||
opponentEng = this.state.eng;
|
||||
opponentWep = this.state.wep;
|
||||
} else {
|
||||
Persist.saveBuild(shipId, newBuildName, code);
|
||||
this._updateRoute(shipId, newBuildName, code);
|
||||
opponentBuild = this.state.opponentBuild;
|
||||
opponent = this.state.opponent;
|
||||
opponentSys = this.state.opponentSys;
|
||||
opponentEng = this.state.opponentEng;
|
||||
opponentWep = this.state.opponentWep;
|
||||
}
|
||||
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code });
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename the current build
|
||||
*/
|
||||
_renameBuild() {
|
||||
let { buildName, newBuildName, shipId, ship } = this.state;
|
||||
const { code, buildName, newBuildName, shipId, ship } = this.state;
|
||||
if (buildName != newBuildName && newBuildName.length) {
|
||||
let code = ship.toString();
|
||||
Persist.deleteBuild(shipId, buildName);
|
||||
Persist.saveBuild(shipId, newBuildName, code);
|
||||
this._updateRoute(shipId, newBuildName, code);
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code });
|
||||
this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,24 +348,50 @@ export default class OutfittingPage extends Page {
|
||||
* Reload build from last save
|
||||
*/
|
||||
_reloadBuild() {
|
||||
this.state.ship.buildFrom(this.state.savedCode);
|
||||
this._shipUpdated();
|
||||
this.setState({ code: this.state.savedCode }, () => this._codeUpdated());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset build to Stock/Factory defaults
|
||||
*/
|
||||
_resetBuild() {
|
||||
this.state.ship.buildWith(Ships[this.state.shipId].defaults);
|
||||
this._shipUpdated();
|
||||
const { ship, shipId, buildName } = this.state;
|
||||
// Rebuild ship
|
||||
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({
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
}, () => this._updateRoute(shipId, buildName, code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the build
|
||||
*/
|
||||
_deleteBuild() {
|
||||
Persist.deleteBuild(this.state.shipId, this.state.buildName);
|
||||
const { shipId, buildName } = this.state;
|
||||
Persist.deleteBuild(shipId, buildName);
|
||||
|
||||
let opponentBuild;
|
||||
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
|
||||
// Our current opponent has been deleted; revert to stock
|
||||
opponentBuild = null;
|
||||
} else {
|
||||
opponentBuild = this.state.opponentBuild;
|
||||
}
|
||||
Router.go(outfitURL(this.state.shipId));
|
||||
|
||||
this.setState({ opponentBuild });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,25 +401,50 @@ export default class OutfittingPage extends Page {
|
||||
let translate = this.context.language.translate;
|
||||
let { buildName, ship } = this.state;
|
||||
this.context.showModal(<ModalExport
|
||||
title={buildName + ' ' + translate('export')}
|
||||
title={(buildName || ship.name) + ' ' + translate('export')}
|
||||
description={translate('PHRASE_EXPORT_DESC')}
|
||||
data={toDetailedBuild(buildName, ship, ship.toString())}
|
||||
/>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger render on ship model change
|
||||
* Called when the code for the ship has been updated, to synchronise the rest of the data
|
||||
*/
|
||||
_codeUpdated() {
|
||||
const { code, ship, shipId, buildName } = this.state;
|
||||
|
||||
// Rebuild ship from the code
|
||||
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({
|
||||
sys,
|
||||
eng,
|
||||
wep,
|
||||
boost,
|
||||
fuel,
|
||||
cargo,
|
||||
opponent,
|
||||
opponentBuild,
|
||||
engagementRange
|
||||
}, () => this._updateRoute(shipId, buildName, code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the ship has been updated, to set the code and then update accordingly
|
||||
*/
|
||||
_shipUpdated() {
|
||||
let { shipId, buildName, ship, fuelCapacity } = this.state;
|
||||
let code = ship.toString();
|
||||
|
||||
if (fuelCapacity != ship.fuelCapacity) {
|
||||
this._fuelChange(this.state.fuelLevel);
|
||||
let { ship, shipId, buildName, cargo, fuel } = this.state;
|
||||
if (cargo > ship.cargoCapacity) {
|
||||
cargo = ship.cargoCapacity;
|
||||
}
|
||||
|
||||
this._updateRoute(shipId, buildName, code);
|
||||
this.setState({ code });
|
||||
if (fuel > ship.fuelCapacity) {
|
||||
fuel = ship.fuelCapacity;
|
||||
}
|
||||
const code = this._fullCode(ship, fuel, cargo);
|
||||
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,36 +457,6 @@ export default class OutfittingPage extends Page {
|
||||
Router.replace(outfitURL(shipId, code, buildName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current fuel level
|
||||
* @param {number} fuelLevel Fuel leval 0 - 1
|
||||
*/
|
||||
_fuelChange(fuelLevel) {
|
||||
let ship = this.state.ship;
|
||||
let fuelCapacity = ship.fuelCapacity;
|
||||
let fuel = fuelCapacity * fuelLevel;
|
||||
this.setState({
|
||||
fuelLevel,
|
||||
fuelCapacity,
|
||||
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuel),
|
||||
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuel),
|
||||
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuel)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimenions from rendered DOM
|
||||
*/
|
||||
_updateDimensions() {
|
||||
let elem = findDOMNode(this.refs.chartThird);
|
||||
|
||||
if (elem) {
|
||||
this.setState({
|
||||
chartWidth: findDOMNode(this.refs.chartThird).offsetWidth
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
@@ -233,7 +464,7 @@ export default class OutfittingPage extends Page {
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
|
||||
this.setState(this._initState(nextContext));
|
||||
this.setState(this._initState(nextProps, nextContext));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,21 +472,51 @@ export default class OutfittingPage extends Page {
|
||||
* Add listeners when about to mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger DOM updates on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
this._updateDimensions();
|
||||
document.addEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this.resizeListener.remove();
|
||||
document.removeEventListener('keydown', this._keyDown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the short URL
|
||||
*/
|
||||
_genShortlink() {
|
||||
this.context.showModal(<ModalPermalink url={window.location.href}/>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open up a window for EDDB with a shopping list of our components
|
||||
*/
|
||||
_eddbShoppingList() {
|
||||
const ship = this.state.ship;
|
||||
|
||||
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);
|
||||
|
||||
// Open up the relevant URL
|
||||
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Key Down
|
||||
* @param {Event} e Keyboard Event
|
||||
*/
|
||||
_keyDown(e) {
|
||||
// .keyCode will eventually be replaced with .key
|
||||
switch (e.keyCode) {
|
||||
case 69: // 'e'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e
|
||||
e.preventDefault();
|
||||
this._exportBuild();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,23 +527,36 @@ 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, chartWidth, fuelCapacity, fuelLevel } = state,
|
||||
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
|
||||
hide = tooltip.bind(null, null),
|
||||
menu = this.props.currentMenu,
|
||||
shipUpdated = this._shipUpdated,
|
||||
canSave = (newBuildName || buildName) && code !== savedCode,
|
||||
canRename = buildName && newBuildName && buildName != newBuildName,
|
||||
canReload = savedCode && canSave,
|
||||
hStr = ship.getHardpointsString(),
|
||||
sStr = ship.getStandardString(),
|
||||
iStr = ship.getInternalString();
|
||||
canReload = savedCode && canSave;
|
||||
|
||||
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
|
||||
code = ship.name + (code || '');
|
||||
|
||||
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
|
||||
const _sStr = ship.getStandardString();
|
||||
const _iStr = ship.getInternalString();
|
||||
const _hStr = ship.getHardpointsString();
|
||||
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
|
||||
const _mStr = ship.getModificationsString();
|
||||
|
||||
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}${ship.cargo}${ship.fuel}`;
|
||||
|
||||
return (
|
||||
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
|
||||
<div id='overview'>
|
||||
<h1>{ship.name}</h1>
|
||||
<div id='build'>
|
||||
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} />
|
||||
<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>
|
||||
@@ -301,87 +575,71 @@ export default class OutfittingPage extends Page {
|
||||
<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>
|
||||
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
|
||||
<LinkIcon className='lg' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ShipSummaryTable ship={ship} code={code} />
|
||||
<StandardSlotSection ship={ship} code={sStr} onChange={shipUpdated} currentMenu={menu} />
|
||||
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
|
||||
<HardpointsSlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
||||
<UtilitySlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
|
||||
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
|
||||
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
|
||||
{/* Main tables */}
|
||||
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
|
||||
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} currentMenu={menu} />
|
||||
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} currentMenu={menu} />
|
||||
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
|
||||
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
|
||||
|
||||
<div ref='chartThird' className='group third'>
|
||||
<h1>{translate('jump range')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenRange}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('LY')}
|
||||
yLabel={translate('jump range')}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.jumpRangeChartFunc}
|
||||
/>
|
||||
{/* Control of ship and opponent */}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('total range')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenFastestRange}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('LY')}
|
||||
yLabel={translate('fastest range')}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.fastestRangeChartFunc}
|
||||
/>
|
||||
<div className='group quarter'>
|
||||
<Pips sys={sys} eng={eng} wep={wep} onChange={this._pipsUpdated} />
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('speed')}</h1>
|
||||
<LineChart
|
||||
width={chartWidth}
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.topBoost + 10}
|
||||
xUnit={translate('T')}
|
||||
yUnit={translate('m/s')}
|
||||
yLabel={translate('speed')}
|
||||
series={SPEED_SERIES}
|
||||
colors={SPEED_COLORS}
|
||||
xLabel={translate('cargo')}
|
||||
func={state.speedChartFunc}
|
||||
/>
|
||||
<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>
|
||||
|
||||
<div className='group half'>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'fuel level')} onMouseLeave={hide}>
|
||||
<Fuel className='xl primary-disabled' />
|
||||
</td>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._fuelChange}
|
||||
axisUnit={translate('T')}
|
||||
percent={fuelLevel}
|
||||
max={fuelCapacity}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
|
||||
{formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<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>
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<EngagementRange ship={ship} engagementRange={engagementRange} onChange={this._engagementRangeUpdated}/>
|
||||
</div>
|
||||
|
||||
{/* Tabbed subpages */}
|
||||
<OutfittingSubpages
|
||||
ship={ship}
|
||||
code={code}
|
||||
buildName={buildName}
|
||||
onChange={shipUpdated}
|
||||
sys={sys}
|
||||
eng={eng}
|
||||
wep={wep}
|
||||
boost={boost}
|
||||
cargo={cargo}
|
||||
fuel={fuel}
|
||||
engagementRange={engagementRange}
|
||||
opponent={opponent}
|
||||
opponentBuild={opponentBuild}
|
||||
opponentSys={opponentSys}
|
||||
opponentEng={opponentEng}
|
||||
opponentWep={opponentWep}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ export default class Page extends React.Component {
|
||||
this[prop] = this[prop].bind(this);
|
||||
}
|
||||
});
|
||||
|
||||
let fix = sessionStorage.getItem('__safari_history_fix');
|
||||
sessionStorage.removeItem('__safari_history_fix');
|
||||
if (fix) {
|
||||
fix = JSON.parse(fix);
|
||||
history.replaceState(history.state, document.title, location.href);
|
||||
history.pushState(fix.state, fix.title, fix.path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,4 +90,4 @@ export default class Page extends React.Component {
|
||||
return this.renderPage();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
31
src/app/pages/ShipyardPage.jsx
Executable file → Normal file
31
src/app/pages/ShipyardPage.jsx
Executable file → Normal file
@@ -40,7 +40,9 @@ function shipSummary(shipId, shipData) {
|
||||
intCount: 0,
|
||||
maxCargo: 0,
|
||||
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
||||
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
|
||||
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
|
||||
};
|
||||
Object.assign(summary, shipData.properties);
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
@@ -55,6 +57,7 @@ function shipSummary(shipId, shipData) {
|
||||
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
||||
summary.topSpeed = ship.topSpeed;
|
||||
summary.topBoost = ship.topBoost;
|
||||
summary.baseArmour = ship.armour;
|
||||
|
||||
return summary;
|
||||
}
|
||||
@@ -82,7 +85,7 @@ export default class ShipyardPage extends Page {
|
||||
}
|
||||
|
||||
this.state = {
|
||||
title: 'Coriolis - Shipyard',
|
||||
title: 'Coriolis EDCD Edition - Shipyard',
|
||||
shipPredicate: 'name',
|
||||
shipDesc: true,
|
||||
shipSummaries: ShipyardPage.cachedShipSummaries
|
||||
@@ -138,15 +141,23 @@ export default class ShipyardPage extends Page {
|
||||
>
|
||||
<td className='le'>{s.manufacturer}</td>
|
||||
<td className='cap'>{translate(SizeMap[s.class])}</td>
|
||||
<td>{s.agility}</td>
|
||||
<td className='ri'>{fInt(s.agility)}</td>
|
||||
<td className='ri'>{fInt(s.hardness)}</td>
|
||||
<td className='ri'>{fInt(s.crew)}</td>
|
||||
<td className='ri'>{fInt(s.speed)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.boost)}{u['m/s']}</td>
|
||||
<td className='ri'>{s.baseArmour}</td>
|
||||
<td className='ri'>{fInt(s.baseArmour)}</td>
|
||||
<td className='ri'>{fInt(s.baseShieldStrength)}{u.MJ}</td>
|
||||
<td className='ri'>{fInt(s.topSpeed)}{u['m/s']}</td>
|
||||
<td className='ri'>{fInt(s.topBoost)}{u['m/s']}</td>
|
||||
<td className='ri'>{fRound(s.maxJumpRange)}{u.LY}</td>
|
||||
<td className='ri'>{fInt(s.maxCargo)}{u.T}</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({ 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>
|
||||
@@ -258,9 +269,12 @@ export default class ShipyardPage extends Page {
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
|
||||
<th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('hardness')}>{translate('hardness')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
|
||||
<th colSpan={4}>{translate('base')}</th>
|
||||
<th colSpan={4}>{translate('max')}</th>
|
||||
<th colSpan={6}>{translate('core module classes')}</th>
|
||||
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
|
||||
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
|
||||
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
|
||||
@@ -278,6 +292,13 @@ export default class ShipyardPage extends Page {
|
||||
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
|
||||
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</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 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>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Module from './Module';
|
||||
|
||||
/**
|
||||
* Calculate the maximum single jump range based on mass and a specific FSD
|
||||
@@ -8,77 +9,847 @@
|
||||
* @return {number} Distance in Light Years
|
||||
*/
|
||||
export function jumpRange(mass, fsd, fuel) {
|
||||
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the fastest (total) range based on mass and a specific FSD, and all fuel available
|
||||
* Calculate the total jump range based on mass and a specific FSD, and all fuel available
|
||||
*
|
||||
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
|
||||
* @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
|
||||
*/
|
||||
export function fastestRange(mass, fsd, fuel) {
|
||||
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
|
||||
let jumps = Math.floor(fuel / fsd.maxfuel);
|
||||
mass += fuelRemaining;
|
||||
// Going backwards, start with the last jump using the remaining fuel
|
||||
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0;
|
||||
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
|
||||
for (let j = 0; j < jumps; j++) {
|
||||
mass += fsd.maxfuel;
|
||||
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
|
||||
export function totalJumpRange(mass, fsd, fuel) {
|
||||
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
||||
|
||||
let fuelRemaining = fuel;
|
||||
let totalRange = 0;
|
||||
while (fuelRemaining > 0) {
|
||||
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
|
||||
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
|
||||
// Mass is reduced
|
||||
mass -= fuelForThisJump;
|
||||
fuelRemaining -= fuelForThisJump;
|
||||
}
|
||||
return fastestRange;
|
||||
return totalRange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
|
||||
*
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @param {number} shields 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
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @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
|
||||
*/
|
||||
export function shieldStrength(mass, shields, sg, multiplier) {
|
||||
let opt;
|
||||
if (mass < sg.minmass) {
|
||||
return shields * multiplier * sg.minmul;
|
||||
}
|
||||
if (mass > sg.maxmass) {
|
||||
return shields * multiplier * sg.maxmul;
|
||||
}
|
||||
if (mass < sg.optmass) {
|
||||
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
|
||||
opt = 1 - Math.pow(1 - opt, 0.87);
|
||||
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
|
||||
} else {
|
||||
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
|
||||
opt = -1 + Math.pow(1 + opt, 2.425);
|
||||
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
|
||||
}
|
||||
export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
// sg might be a module or a template; handle either here
|
||||
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
|
||||
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
|
||||
let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the a ships speed based on mass, and thrusters.
|
||||
*
|
||||
* @param {number} mass Current mass of the ship
|
||||
* @param {number} baseSpeed Base speed m/s for ship
|
||||
* @param {number} baseBoost Base boost speed m/s for ship
|
||||
* @param {object} thrusters The Thrusters used
|
||||
* @param {number} pipSpeed Speed pip multiplier
|
||||
* @return {object} Approximate speed by pips
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseSpeed base speed m/s for ship
|
||||
* @param {object} thrusters The ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Speed by pips
|
||||
*/
|
||||
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
|
||||
let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P)));
|
||||
let speed = baseSpeed * multiplier;
|
||||
export function speed(mass, baseSpeed, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
|
||||
return {
|
||||
'0 Pips': speed * (1 - (pipSpeed * 4)),
|
||||
'2 Pips': speed * (1 - (pipSpeed * 2)),
|
||||
'4 Pips': speed,
|
||||
'boost': baseBoost * multiplier
|
||||
};
|
||||
let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pitch of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} basePitch base pitch of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Pitch by pips
|
||||
*/
|
||||
export function pitch(mass, basePitch, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate yaw of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseYaw base yaw of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Yaw by pips
|
||||
*/
|
||||
export function yaw(mass, baseYaw, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate roll of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseRoll base roll of the ship
|
||||
* @param {object} thrusters the ship's thrusters
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} Roll by pips
|
||||
*/
|
||||
export function roll(mass, baseRoll, thrusters, engpip) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise according to FD's calculations and return suitable values
|
||||
* @param {number} minMass the minimum mass of the thrusters
|
||||
* @param {number} optMass the optimum mass of the thrusters
|
||||
* @param {number} maxMass the maximum mass of the thrusters
|
||||
* @param {number} minMul the minimum multiplier of the thrusters
|
||||
* @param {number} optMul the optimum multiplier of the thrusters
|
||||
* @param {number} maxMul the maximum multiplier of the thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {base} base the base value from which to calculate
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @return {array} values by pips
|
||||
*/
|
||||
function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip) {
|
||||
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
const ynorm = Math.pow(xnorm, exponent);
|
||||
const mul = minMul + ynorm * (maxMul - minMul);
|
||||
const res = base * mul;
|
||||
|
||||
return [res * (1 - (engpip * 4)),
|
||||
res * (1 - (engpip * 3)),
|
||||
res * (1 - (engpip * 2)),
|
||||
res * (1 - (engpip * 1)),
|
||||
res];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a single value
|
||||
* @param {number} minMass the minimum mass of the thrusters
|
||||
* @param {number} optMass the optimum mass of the thrusters
|
||||
* @param {number} maxMass the maximum mass of the thrusters
|
||||
* @param {number} minMul the minimum multiplier of the thrusters
|
||||
* @param {number} optMul the optimum multiplier of the thrusters
|
||||
* @param {number} maxMul the maximum multiplier of the thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {base} base the base value from which to calculate
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @returns {number} the resultant value
|
||||
*/
|
||||
function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) {
|
||||
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
|
||||
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
|
||||
const ynorm = Math.pow(xnorm, exponent);
|
||||
const mul = minMul + ynorm * (maxMul - minMul);
|
||||
const res = base * mul;
|
||||
|
||||
return res * (1 - (engpip * (4 - eng)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
* @param {boolean} boost true if the boost is activated
|
||||
* @returns {number} the resultant speed
|
||||
*/
|
||||
export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
|
||||
|
||||
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip, eng);
|
||||
if (boost == true) {
|
||||
result *= boostFactor;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
* @param {boolean} boost true if the boost is activated
|
||||
* @returns {number} the resultant pitch
|
||||
*/
|
||||
export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip, eng);
|
||||
if (boost == true) {
|
||||
result *= boostFactor;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate roll for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseRoll the base roll of the ship
|
||||
* @param {ojbect} 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
|
||||
* @param {boolean} boost true if the boost is activated
|
||||
* @returns {number} the resultant roll
|
||||
*/
|
||||
export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip, eng);
|
||||
if (boost == true) {
|
||||
result *= boostFactor;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate yaw for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseYaw the base yaw of the ship
|
||||
* @param {ojbect} 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
|
||||
* @param {boolean} boost true if the boost is activated
|
||||
* @returns {number} the resultant yaw
|
||||
*/
|
||||
export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) {
|
||||
// thrusters might be a module or a template; handle either here
|
||||
let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
|
||||
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
|
||||
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
|
||||
let minMul = thrusters instanceof Module ? thrusters.getMinMul('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
|
||||
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
|
||||
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
|
||||
|
||||
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip, eng);
|
||||
if (boost == true) {
|
||||
result *= boostFactor;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate shield metrics
|
||||
* @param {Object} ship The ship
|
||||
* @param {int} sys The pips to SYS
|
||||
* @returns {Object} Shield metrics
|
||||
*/
|
||||
export function shieldMetrics(ship, sys) {
|
||||
const sysResistance = this.sysResistance(sys);
|
||||
const maxSysResistance = this.sysResistance(4);
|
||||
|
||||
let shield = {};
|
||||
|
||||
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
||||
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
||||
const shieldGenerator = shieldGeneratorSlot.m;
|
||||
|
||||
// Boosters
|
||||
let boost = 1;
|
||||
let boosterExplDmg = 1;
|
||||
let boosterKinDmg = 1;
|
||||
let boosterThermDmg = 1;
|
||||
for (let slot of ship.hardpoints) {
|
||||
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
||||
boost += slot.m.getShieldBoost();
|
||||
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
||||
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);
|
||||
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 powerDistributor = ship.standard[4].m;
|
||||
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
|
||||
|
||||
// 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
|
||||
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
|
||||
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||
|
||||
let recover = 16;
|
||||
if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
|
||||
// We can recover the entire shield from the capacitor store
|
||||
recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
|
||||
} else {
|
||||
// We can recover some of the shield from the capacitor store
|
||||
recover += capacitorLifetime;
|
||||
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
|
||||
if (sys === 0) {
|
||||
// No system pips so will never recover shields
|
||||
recover = Math.Inf;
|
||||
} else {
|
||||
// Recover remaining shields at the rate of the power distributor's recharge
|
||||
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
// Recharge time is the time taken to go from 50% to 100%
|
||||
const shieldToRecharge = (generatorStrength + boostersStrength) / 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
|
||||
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
|
||||
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||
|
||||
let recharge = 0;
|
||||
if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
|
||||
// We can recharge the entire shield from the capacitor store
|
||||
recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
|
||||
} else {
|
||||
// We can recharge some of the shield from the capacitor store
|
||||
recharge += capacitorLifetime;
|
||||
const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
|
||||
if (sys === 0) {
|
||||
// No system pips so will never recharge shields
|
||||
recharge = Math.Inf;
|
||||
} else {
|
||||
// Recharge remaining shields at the rate of the power distributor's recharge
|
||||
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
shield = {
|
||||
generator: generatorStrength,
|
||||
boosters: boostersStrength,
|
||||
cells: ship.shieldCells,
|
||||
total: generatorStrength + boostersStrength + ship.shieldCells,
|
||||
recover,
|
||||
recharge,
|
||||
};
|
||||
|
||||
// Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
|
||||
// We re-cast these as damage percentages
|
||||
shield.absolute = {
|
||||
generator: 1,
|
||||
boosters: 1,
|
||||
sys: 1 - sysResistance,
|
||||
total: 1 - sysResistance,
|
||||
max: 1 - maxSysResistance
|
||||
};
|
||||
|
||||
shield.explosive = {
|
||||
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
||||
boosters: boosterExplDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
|
||||
};
|
||||
|
||||
shield.kinetic = {
|
||||
generator: 1 - shieldGenerator.getKineticResistance(),
|
||||
boosters: boosterKinDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
|
||||
};
|
||||
|
||||
shield.thermal = {
|
||||
generator: 1 - shieldGenerator.getThermalResistance(),
|
||||
boosters: boosterThermDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
|
||||
};
|
||||
}
|
||||
|
||||
return shield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate armour metrics
|
||||
* @param {Object} ship The ship
|
||||
* @returns {Object} Armour metrics
|
||||
*/
|
||||
export function armourMetrics(ship) {
|
||||
// Armour from bulkheads
|
||||
const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
|
||||
let armourReinforcement = 0;
|
||||
|
||||
let moduleArmour = 0;
|
||||
let moduleProtection = 1;
|
||||
|
||||
let hullExplDmg = 1;
|
||||
let hullKinDmg = 1;
|
||||
let hullThermDmg = 1;
|
||||
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of ship.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
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());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
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,
|
||||
modulearmour: moduleArmour,
|
||||
moduleprotection: moduleProtection,
|
||||
total: armourBulkheads + armourReinforcement
|
||||
};
|
||||
|
||||
// Armour resistances have two components: bulkheads and HRPs
|
||||
// We re-cast these as damage percentages
|
||||
armour.absolute = {
|
||||
bulkheads: 1,
|
||||
reinforcement: 1,
|
||||
total: 1
|
||||
};
|
||||
|
||||
armour.explosive = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
|
||||
reinforcement: hullExplDmg,
|
||||
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
|
||||
};
|
||||
|
||||
armour.kinetic = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
|
||||
reinforcement: hullKinDmg,
|
||||
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
|
||||
};
|
||||
|
||||
armour.thermal = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
|
||||
reinforcement: hullThermDmg,
|
||||
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
|
||||
};
|
||||
|
||||
return armour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate defence metrics for a ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {int} sys The pips to SYS
|
||||
* @param {int} opponentWep The pips to pponent's WEP
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Defence metrics
|
||||
*/
|
||||
export function defenceMetrics(ship, opponent, sys, opponentWep, engagementrange) {
|
||||
// Obtain the shield metrics
|
||||
const shield = this.shieldMetrics(ship, sys);
|
||||
|
||||
// Obtain the armour metrics
|
||||
const armour = this.armourMetrics(ship);
|
||||
|
||||
// Obtain the opponent's sustained DPS on us
|
||||
const sustainedDps = this.sustainedDps(opponent, ship, sys, engagementrange);
|
||||
|
||||
const shielddamage = shield.generator ? {
|
||||
absolutesdps: sustainedDps.shieldsdps.absolute,
|
||||
explosivesdps: sustainedDps.shieldsdps.explosive,
|
||||
kineticsdps: sustainedDps.shieldsdps.kinetic,
|
||||
thermalsdps: sustainedDps.shieldsdps.thermal,
|
||||
totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal,
|
||||
totalseps: sustainedDps.eps
|
||||
} : {};
|
||||
|
||||
const armourdamage = {
|
||||
absolutesdps: sustainedDps.armoursdps.absolute,
|
||||
explosivesdps: sustainedDps.armoursdps.explosive,
|
||||
kineticsdps: sustainedDps.armoursdps.kinetic,
|
||||
thermalsdps: sustainedDps.armoursdps.thermal,
|
||||
totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal,
|
||||
totalseps: sustainedDps.eps
|
||||
};
|
||||
|
||||
return { shield, armour, shielddamage, armourdamage };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate offence metrics for a ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {int} wep The pips to WEP
|
||||
* @param {int} opponentSys The pips to opponent's SYS
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {array} Offence metrics
|
||||
*/
|
||||
export function offenceMetrics(ship, opponent, wep, opponentSys, engagementrange) {
|
||||
// Per-weapon and total damage
|
||||
const damage = [];
|
||||
|
||||
// Obtain the opponent's shield and armour metrics
|
||||
const opponentShields = this.shieldMetrics(opponent, opponentSys);
|
||||
const opponentArmour = this.armourMetrics(opponent);
|
||||
|
||||
// Per-weapon and total damage to shields
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
|
||||
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
let engineering;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
engineering = m.blueprint.name + ' ' + 'grade' + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
engineering += ', ' + m.blueprint.special.name;
|
||||
}
|
||||
}
|
||||
|
||||
const weaponSustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
damage.push({
|
||||
id: i,
|
||||
mount: m.mount,
|
||||
name: m.name || m.grp,
|
||||
classRating,
|
||||
engineering,
|
||||
sdps: weaponSustainedDps.damage,
|
||||
seps: weaponSustainedDps.eps,
|
||||
effectiveness: weaponSustainedDps.effectiveness
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return damage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the resistance provided by SYS pips
|
||||
* @param {integer} sys the value of the SYS pips
|
||||
* @returns {integer} the resistance for the given pips
|
||||
*/
|
||||
export function sysResistance(sys) {
|
||||
return Math.pow(sys, 0.85) * 0.6 / Math.pow(4, 0.85);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the recharge rate of the SYS capacitor of a power distributor given pips
|
||||
* @param {Object} pd The power distributor
|
||||
* @param {number} sys The number of pips to SYS
|
||||
* @returns {number} The recharge rate in MJ/s
|
||||
*/
|
||||
export function sysRechargeRate(pd, sys) {
|
||||
return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a ship against an opponent at a given range
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {number} sys Pips to opponent's SYS
|
||||
* @param {int} engagementrange The range between the ship and opponent
|
||||
* @returns {Object} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function sustainedDps(ship, opponent, sys, engagementrange) {
|
||||
// Obtain the opponent's shield and armour metrics
|
||||
const opponentShields = this.shieldMetrics(opponent, sys);
|
||||
const opponentArmour = this.armourMetrics(opponent);
|
||||
|
||||
return this._sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a ship against an opponent at a given range
|
||||
* @param {Object} ship The 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
|
||||
*/
|
||||
export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const shieldsdps = {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0
|
||||
};
|
||||
|
||||
const armoursdps = {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0
|
||||
};
|
||||
|
||||
let eps = 0;
|
||||
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
const sustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
|
||||
shieldsdps.absolute += sustainedDps.damage.shields.absolute;
|
||||
shieldsdps.explosive += sustainedDps.damage.shields.explosive;
|
||||
shieldsdps.kinetic += sustainedDps.damage.shields.kinetic;
|
||||
shieldsdps.thermal += sustainedDps.damage.shields.thermal;
|
||||
armoursdps.absolute += sustainedDps.damage.armour.absolute;
|
||||
armoursdps.explosive += sustainedDps.damage.armour.explosive;
|
||||
armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
|
||||
armoursdps.thermal += sustainedDps.damage.armour.thermal;
|
||||
eps += sustainedDps.eps;
|
||||
}
|
||||
}
|
||||
|
||||
return { shieldsdps, armoursdps, eps };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a weapon at a given range
|
||||
* @param {Object} m The weapon
|
||||
* @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
|
||||
*/
|
||||
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const opponentHasShields = opponentShields.generator ? true : false;
|
||||
const weapon = {
|
||||
eps: 0,
|
||||
damage: {
|
||||
shields: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0
|
||||
},
|
||||
armour: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0
|
||||
},
|
||||
},
|
||||
effectiveness: {
|
||||
shields: {
|
||||
range: 1,
|
||||
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
|
||||
resistance: 1
|
||||
},
|
||||
armour: {
|
||||
range: 1,
|
||||
hardness: 1,
|
||||
resistance: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// EPS
|
||||
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();
|
||||
|
||||
// Take fall-off in to account
|
||||
const falloff = m.getFalloff();
|
||||
if (falloff && engagementrange > falloff) {
|
||||
const dropoffRange = m.getRange() - falloff;
|
||||
const dropoff = 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
|
||||
weapon.effectiveness.shields.range = weapon.effectiveness.armour.range = dropoff;
|
||||
sDps *= dropoff;
|
||||
}
|
||||
|
||||
// Piercing/hardness modifier (for armour only)
|
||||
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
||||
weapon.effectiveness.armour.hardness = armourMultiple;
|
||||
|
||||
// Break out the damage according to type
|
||||
let shieldsResistance = 0;
|
||||
let armourResistance = 0;
|
||||
if (m.getDamageDist().A) {
|
||||
weapon.damage.shields.absolute += sDps * m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.total : 1);
|
||||
weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
|
||||
shieldsResistance += m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.generator * opponentShields.absolute.boosters : 1);
|
||||
armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().E) {
|
||||
weapon.damage.shields.explosive += sDps * m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.total : 1);
|
||||
weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
|
||||
shieldsResistance += m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.generator * opponentShields.explosive.boosters : 1);
|
||||
armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().K) {
|
||||
weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.total : 1);
|
||||
weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
|
||||
shieldsResistance += m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.generator * opponentShields.kinetic.boosters : 1);
|
||||
armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
|
||||
}
|
||||
if (m.getDamageDist().T) {
|
||||
weapon.damage.shields.thermal += sDps * m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.total : 1);
|
||||
weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
|
||||
shieldsResistance += m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.generator * opponentShields.thermal.boosters : 1);
|
||||
armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
|
||||
}
|
||||
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;
|
||||
return weapon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time to drain WEP capacitor
|
||||
* @param {object} ship The ship
|
||||
* @param {number} wep Pips to WEP
|
||||
* @returns {number} The time to drain the WEP capacitor, in seconds
|
||||
*/
|
||||
export function timeToDrainWep(ship, wep) {
|
||||
let totalSEps = 0;
|
||||
|
||||
for (let slotNum in ship.hardpoints) {
|
||||
const slot = ship.hardpoints[slotNum];
|
||||
if (slot.maxClass > 0 && slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
|
||||
totalSEps += slot.m.getClip() ? (slot.m.getClip() * slot.m.getEps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : slot.m.getEps();
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the drain time
|
||||
const drainPerSecond = totalSEps - ship.standard[4].m.getWeaponsRechargeRate() * wep / 4;
|
||||
if (drainPerSecond <= 0) {
|
||||
// Can fire forever
|
||||
return Infinity;
|
||||
} else {
|
||||
const initialCharge = ship.standard[4].m.getWeaponsCapacity();
|
||||
return initialCharge / drainPerSecond;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the time to deplete an amount of shields or armour
|
||||
* @param {number} amount The amount to be depleted
|
||||
* @param {number} dps The depletion per second
|
||||
* @param {number} eps The energy drained per second
|
||||
* @param {number} capacity The initial energy capacity
|
||||
* @param {number} recharge The energy recharged per second
|
||||
* @returns {number} The number of seconds to deplete to 0
|
||||
*/
|
||||
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||
const drainPerSecond = eps - recharge;
|
||||
if (drainPerSecond <= 0) {
|
||||
// Simple result
|
||||
return amount / dps;
|
||||
} else {
|
||||
// We are draining the capacitor, but can we deplete before we run out
|
||||
const timeToDrain = capacity / drainPerSecond;
|
||||
const depletedBeforeDrained = dps * timeToDrain;
|
||||
if (depletedBeforeDrained >= amount) {
|
||||
return amount / dps;
|
||||
} else {
|
||||
const restToDeplete = amount - depletedBeforeDrained;
|
||||
// We delete the rest at the reduced rate
|
||||
const reducedDps = dps * (recharge / eps);
|
||||
return timeToDrain + (restToDeplete / reducedDps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
|
||||
export const ArmourMultiplier = [
|
||||
1, // Lightweight
|
||||
1.4, // Reinforced
|
||||
1.945, // Military
|
||||
1.945, // Mirrored
|
||||
1.945 // Reactive
|
||||
];
|
||||
|
||||
export const SizeMap = ['', 'small', 'medium', 'large', 'capital'];
|
||||
|
||||
export const StandardArray = [
|
||||
@@ -37,9 +29,11 @@ export const ModuleGroupToName = {
|
||||
am: 'Auto Field-Maintenance Unit',
|
||||
bsg: 'Bi-Weave Shield Generator',
|
||||
cr: 'Cargo Rack',
|
||||
fh: 'Fighter Hangar',
|
||||
fi: 'Frame Shift Drive Interdictor',
|
||||
hb: 'Hatch Breaker Limpet Controller',
|
||||
hr: 'Hull Reinforcement Package',
|
||||
mrp: 'Module Reinforcement Package',
|
||||
rf: 'Refinery',
|
||||
scb: 'Shield Cell Bank',
|
||||
sg: 'Shield Generator',
|
||||
@@ -48,21 +42,30 @@ export const ModuleGroupToName = {
|
||||
dc: 'Docking Computer',
|
||||
fx: 'Fuel Transfer Limpet Controller',
|
||||
pc: 'Prospector Limpet Controller',
|
||||
pce: 'Economy Class Passenger Cabin',
|
||||
pci: 'Business Class Passenger Cabin',
|
||||
pcm: 'First Class Passenger Cabin',
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
cc: 'Collector Limpet Controller',
|
||||
ss: 'Surface Scanner',
|
||||
|
||||
// Hard Points
|
||||
bl: 'Beam Laser',
|
||||
ul: 'Burst Laser',
|
||||
c: 'Cannon',
|
||||
ch: 'Chaff Launcher',
|
||||
cs: 'Cargo Scanner',
|
||||
cm: 'Countermeasure',
|
||||
ec: 'Electronic Countermeasure',
|
||||
fc: 'Fragment Cannon',
|
||||
hs: 'Heat Sink Launcher',
|
||||
ws: 'Frame Shift Wake Scanner',
|
||||
kw: 'Kill Warrant Scanner',
|
||||
nl: 'Mine Launcher',
|
||||
ml: 'Mining Laser',
|
||||
mr: 'Missile Rack',
|
||||
pa: 'Plasma Accelerator',
|
||||
po: 'Point Defence',
|
||||
mc: 'Multi-cannon',
|
||||
pl: 'Pulse Laser',
|
||||
rg: 'Rail Gun',
|
||||
@@ -104,8 +107,9 @@ export const BulkheadNames = [
|
||||
export const ShipFacets = [
|
||||
{ // 0
|
||||
title: 'agility',
|
||||
props: ['agility'],
|
||||
fmt: 'int',
|
||||
props: ['topPitch', 'topRoll', 'topYaw'],
|
||||
lbls: ['pitch', 'roll', 'yaw'],
|
||||
fmt: 'f1',
|
||||
i: 0
|
||||
},
|
||||
{ // 1
|
||||
@@ -124,7 +128,7 @@ export const ShipFacets = [
|
||||
},
|
||||
{ // 3
|
||||
title: 'shields',
|
||||
props: ['shieldStrength'],
|
||||
props: ['shield'],
|
||||
unit: 'MJ',
|
||||
fmt: 'int',
|
||||
i: 3
|
||||
@@ -184,10 +188,31 @@ export const ShipFacets = [
|
||||
},
|
||||
{ // 11
|
||||
title: 'DPS',
|
||||
props: ['totalDps'],
|
||||
lbls: ['DPS'],
|
||||
props: ['totalDps', 'totalExplDps', 'totalKinDps', 'totalThermDps'],
|
||||
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
|
||||
fmt: 'round',
|
||||
i: 11
|
||||
},
|
||||
{ // 14
|
||||
title: 'Sustained DPS',
|
||||
props: ['totalSDps', 'totalExplSDps', 'totalKinSDps', 'totalThermSDps'],
|
||||
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
|
||||
fmt: 'round',
|
||||
i: 14
|
||||
},
|
||||
{ // 12
|
||||
title: 'EPS',
|
||||
props: ['totalEps'],
|
||||
lbls: ['EPS'],
|
||||
fmt: 'round',
|
||||
i: 12
|
||||
},
|
||||
{ // 13
|
||||
title: 'HPS',
|
||||
props: ['totalHps'],
|
||||
lbls: ['HPS'],
|
||||
fmt: 'round',
|
||||
i: 13
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
15
src/app/shipyard/Modification.js
Executable file
15
src/app/shipyard/Modification.js
Executable file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Modification - a modification and its value
|
||||
*/
|
||||
export default class Modification {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique modification ID
|
||||
* @param {Number} value Value of the modification
|
||||
*/
|
||||
constructor(id, value) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
724
src/app/shipyard/Module.js
Executable file
724
src/app/shipyard/Module.js
Executable file
@@ -0,0 +1,724 @@
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
|
||||
/**
|
||||
* Module - active module in a ship's buildout
|
||||
*/
|
||||
export default class Module {
|
||||
|
||||
/**
|
||||
* Construct a new module
|
||||
* @param {Object} params Module parameters. Either grp/id or template
|
||||
*/
|
||||
constructor(params) {
|
||||
let properties = Object.assign({ grp: null, id: null, template: null }, params);
|
||||
|
||||
if (properties.class != undefined) {
|
||||
// We already have a fully-formed module; copy the data over
|
||||
for (let p in properties) { this[p] = properties[p]; }
|
||||
} else if (properties.template != undefined) {
|
||||
// We have a template from coriolis-data; copy the data over
|
||||
for (let p in properties.template) { this[p] = properties.template[p]; }
|
||||
} else {
|
||||
// We don't have a template; find it given the group and ID
|
||||
return ModuleUtils.findModule(properties.grp, properties.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing module
|
||||
* @return {Object} A clone of the existing module
|
||||
*/
|
||||
clone() {
|
||||
return new Module(JSON.parse(JSON.stringify(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value for a given modification
|
||||
* @param {Number} name The name of the modification
|
||||
* @param {Number} raw True if the value returned should be raw i.e. without the influence of special effects
|
||||
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
|
||||
*/
|
||||
getModValue(name, raw) {
|
||||
let result = this.mods && this.mods[name] ? this.mods[name] : null;
|
||||
if ((!raw) && this.blueprint && this.blueprint.special) {
|
||||
// This module has a special effect, see if we need to alter our returned value
|
||||
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
|
||||
if (modifierActions && modifierActions[name]) {
|
||||
// this special effect modifies our returned value
|
||||
const modification = Modifications.modifications[name];
|
||||
if (modification.method === 'additive') {
|
||||
result = result + modifierActions[name] * 100;
|
||||
} else if (modification.method === 'overwrite') {
|
||||
result = modifierActions[name];
|
||||
} else {
|
||||
// rate of fire is special, as it's really burst interval. Handle that here
|
||||
let mod = null;
|
||||
if (name === 'rof') {
|
||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
||||
} else {
|
||||
mod = modifierActions[name];
|
||||
}
|
||||
const multiplier = modification.type === 'percentage' ? 10000 : 100;
|
||||
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitise the resultant value to 4dp equivalent
|
||||
return isNaN(result) ? result : Math.round(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value for a given modification ID
|
||||
* @param {Number} name The name of the modification
|
||||
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
|
||||
* @param {bool} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
|
||||
*/
|
||||
setModValue(name, value, valueiswithspecial) {
|
||||
if (!this.mods) {
|
||||
this.mods = {};
|
||||
}
|
||||
if (valueiswithspecial && this.blueprint && this.blueprint.special) {
|
||||
// This module has a special effect, see if we need to alter the stored value
|
||||
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
|
||||
if (modifierActions && modifierActions[name]) {
|
||||
// This special effect modifies the value being set, so we need to revert it prior to storing the value
|
||||
const modification = Modifications.modifications[name];
|
||||
if (modification.method === 'additive') {
|
||||
value = value - modifierActions[name];
|
||||
} else if (modification.method === 'overwrite') {
|
||||
value = null;
|
||||
} else {
|
||||
// rate of fire is special, as it's really burst interval. Handle that here
|
||||
let mod = null;
|
||||
if (name === 'rof') {
|
||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
||||
} else {
|
||||
mod = modifierActions[name];
|
||||
}
|
||||
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null || value == 0) {
|
||||
delete this.mods[name];
|
||||
} else {
|
||||
this.mods[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to obtain a modified value using standard multipliers
|
||||
* @param {String} name the name of the modifier to obtain
|
||||
* @return {Number} the mass of this module
|
||||
*/
|
||||
_getModifiedValue(name) {
|
||||
const modification = Modifications.modifications[name];
|
||||
let result = this[name];
|
||||
|
||||
if (!result) {
|
||||
if (modification && modification.method === 'additive') {
|
||||
// Additive modifications start at 0 rather than NULL
|
||||
result = 0;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
if (modification) {
|
||||
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
|
||||
// we divide by 100. Both ways we end up with a value with two decimal places
|
||||
let modValue;
|
||||
if (modification.type === 'percentage') {
|
||||
modValue = this.getModValue(name) / 10000;
|
||||
} else if (modification.type === 'numeric') {
|
||||
modValue = this.getModValue(name) / 100;
|
||||
} else {
|
||||
modValue = this.getModValue(name);
|
||||
}
|
||||
if (modValue) {
|
||||
if (modification.method === 'additive') {
|
||||
result = result + modValue;
|
||||
} else if (modification.method === 'overwrite') {
|
||||
result = modValue;
|
||||
} else {
|
||||
result = result * (1 + modValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (name === 'burst') {
|
||||
// Burst is special, as if it can not exist but have a modification
|
||||
result = this.getModValue(name) / 100;
|
||||
} else if (name === 'burstrof') {
|
||||
// Burst rate of fire is special, as if it can not exist but have a modification
|
||||
result = this.getModValue(name) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the power generation of this module, taking in to account modifications
|
||||
* @return {Number} the power generation of this module
|
||||
*/
|
||||
getPowerGeneration() {
|
||||
return this._getModifiedValue('pgen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the power usage of this module, taking in to account modifications
|
||||
* @return {Number} the power usage of this module
|
||||
*/
|
||||
getPowerUsage() {
|
||||
return this._getModifiedValue('power');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integrity of this module, taking in to account modifications
|
||||
* @return {Number} the integrity of this module
|
||||
*/
|
||||
getIntegrity() {
|
||||
return this._getModifiedValue('integrity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mass of this module, taking in to account modifications
|
||||
* @return {Number} the mass of this module
|
||||
*/
|
||||
getMass() {
|
||||
return this._getModifiedValue('mass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal efficiency of this module, taking in to account modifications
|
||||
* @return {Number} the thermal efficiency of this module
|
||||
*/
|
||||
getThermalEfficiency() {
|
||||
return this._getModifiedValue('eff');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum fuel per jump for this module, taking in to account modifications
|
||||
* @return {Number} the maximum fuel per jump of this module
|
||||
*/
|
||||
getMaxFuelPerJump() {
|
||||
return this._getModifiedValue('maxfuel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the systems capacity for this module, taking in to account modifications
|
||||
* @return {Number} the systems capacity of this module
|
||||
*/
|
||||
getSystemsCapacity() {
|
||||
return this._getModifiedValue('syscap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the engines capacity for this module, taking in to account modifications
|
||||
* @return {Number} the engines capacity of this module
|
||||
*/
|
||||
getEnginesCapacity() {
|
||||
return this._getModifiedValue('engcap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weapons capacity for this module, taking in to account modifications
|
||||
* @return {Number} the weapons capacity of this module
|
||||
*/
|
||||
getWeaponsCapacity() {
|
||||
return this._getModifiedValue('wepcap');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the systems recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the systems recharge rate of this module
|
||||
*/
|
||||
getSystemsRechargeRate() {
|
||||
return this._getModifiedValue('sysrate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the engines recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the engines recharge rate of this module
|
||||
*/
|
||||
getEnginesRechargeRate() {
|
||||
return this._getModifiedValue('engrate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weapons recharge rate for this module, taking in to account modifications
|
||||
* @return {Number} the weapons recharge rate of this module
|
||||
*/
|
||||
getWeaponsRechargeRate() {
|
||||
return this._getModifiedValue('weprate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kinetic resistance for this module, taking in to account modifications
|
||||
* @return {Number} the kinetic resistance of this module
|
||||
*/
|
||||
getKineticResistance() {
|
||||
return this._getModifiedValue('kinres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal resistance for this module, taking in to account modifications
|
||||
* @return {Number} the thermal resistance of this module
|
||||
*/
|
||||
getThermalResistance() {
|
||||
return this._getModifiedValue('thermres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the explosive resistance for this module, taking in to account modifications
|
||||
* @return {Number} the explosive resistance of this module
|
||||
*/
|
||||
getExplosiveResistance() {
|
||||
return this._getModifiedValue('explres');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the regeneration rate for this module, taking in to account modifications
|
||||
* @return {Number} the regeneration rate of this module
|
||||
*/
|
||||
getRegenerationRate() {
|
||||
return this._getModifiedValue('regen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broken regeneration rate for this module, taking in to account modifications
|
||||
* @return {Number} the broken regeneration rate of this module
|
||||
*/
|
||||
getBrokenRegenerationRate() {
|
||||
return this._getModifiedValue('brokenregen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for this module, taking in to account modifications
|
||||
* @return {Number} the range rate of this module
|
||||
*/
|
||||
getRange() {
|
||||
return this._getModifiedValue('range');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the falloff for this module, taking in to account modifications
|
||||
* @return {Number} the falloff of this module
|
||||
*/
|
||||
getFalloff() {
|
||||
if (this.getModValue('fallofffromrange')) {
|
||||
// Falloff from range means what it says, so use range instead of falloff
|
||||
return this.getRange();
|
||||
} else {
|
||||
// Need to find out if we have a focused modification, in which case our falloff is scaled to range
|
||||
if (this.blueprint && this.blueprint.name === 'Focused') {
|
||||
const rangeMod = this.getModValue('range') / 10000;
|
||||
return this.falloff * (1 + rangeMod);
|
||||
} else {
|
||||
// Standard falloff calculation
|
||||
const range = this.getRange();
|
||||
const falloff = this._getModifiedValue('falloff');
|
||||
return (falloff > range ? range : falloff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range (in terms of seconds, for FSDI) for this module, taking in to account modifications
|
||||
* @return {Number} the range of this module
|
||||
*/
|
||||
getRangeT() {
|
||||
return this._getModifiedValue('ranget');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scan time for this module, taking in to account modifications
|
||||
* @return {Number} the scan time of this module
|
||||
*/
|
||||
getScanTime() {
|
||||
return this._getModifiedValue('scantime');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the capture arc for this module, taking in to account modifications
|
||||
* @return {Number} the capture arc of this module
|
||||
*/
|
||||
getCaptureArc() {
|
||||
return this._getModifiedValue('arc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hull reinforcement for this module, taking in to account modifications
|
||||
* @return {Number} the hull reinforcement of this module
|
||||
*/
|
||||
getHullReinforcement() {
|
||||
return this._getModifiedValue('hullreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the protection for this module, taking in to account modifications
|
||||
* @return {Number} the protection of this module
|
||||
*/
|
||||
getProtection() {
|
||||
return this._getModifiedValue('protection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the delay for this module, taking in to account modifications
|
||||
* @return {Number} the delay of this module
|
||||
*/
|
||||
getDelay() {
|
||||
return this._getModifiedValue('delay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration for this module, taking in to account modifications
|
||||
* @return {Number} the duration of this module
|
||||
*/
|
||||
getDuration() {
|
||||
return this._getModifiedValue('duration');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shield boost for this module, taking in to account modifications
|
||||
* @return {Number} the shield boost of this module
|
||||
*/
|
||||
getShieldBoost() {
|
||||
return this._getModifiedValue('shieldboost');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum mass for this module, taking in to account modifications
|
||||
* @return {Number} the minimum mass of this module
|
||||
*/
|
||||
getMinMass() {
|
||||
// Modifier is optmass
|
||||
let result = 0;
|
||||
if (this['minmass']) {
|
||||
result = this['minmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimum mass for this module, taking in to account modifications
|
||||
* @return {Number} the optimum mass of this module
|
||||
*/
|
||||
getOptMass() {
|
||||
return this._getModifiedValue('optmass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum mass for this module, taking in to account modifications
|
||||
* @return {Number} the maximum mass of this module
|
||||
*/
|
||||
getMaxMass() {
|
||||
// Modifier is optmass
|
||||
let result = 0;
|
||||
if (this['maxmass']) {
|
||||
result = this['maxmass'];
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmass') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the minimum multiplier of this module
|
||||
*/
|
||||
getMinMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['minmul' + type]) {
|
||||
result = this['minmul' + type];
|
||||
} else if (this['minmul']) {
|
||||
result = this['minmul'];
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the optimum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the optimum multiplier of this module
|
||||
*/
|
||||
getOptMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['optmul' + type]) {
|
||||
result = this['optmul' + type];
|
||||
} else if (this['optmul']) {
|
||||
result = this['optmul'];
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum multiplier for this module, taking in to account modifications
|
||||
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
|
||||
* @return {Number} the maximum multiplier of this module
|
||||
*/
|
||||
getMaxMul(type = null) {
|
||||
// Modifier is optmul
|
||||
let result = 0;
|
||||
if (this['maxmul' + type]) {
|
||||
result = this['maxmul' + type];
|
||||
} else if (this['maxmul']) {
|
||||
result = this['maxmul'];
|
||||
}
|
||||
if (result) {
|
||||
let mult = this.getModValue('optmul') / 10000;
|
||||
if (mult) { result = result * (1 + mult); }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the damage for this module, taking in to account modifications
|
||||
* @return {Number} the damage of this module
|
||||
*/
|
||||
getDamage() {
|
||||
return this._getModifiedValue('damage');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distributor draw for this module, taking in to account modifications
|
||||
* @return {Number} the distributor draw of this module
|
||||
*/
|
||||
getDistDraw() {
|
||||
return this._getModifiedValue('distdraw');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thermal load for this module, taking in to account modifications
|
||||
* @return {Number} the thermal load of this module
|
||||
*/
|
||||
getThermalLoad() {
|
||||
return this._getModifiedValue('thermload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rounds per shot for this module, taking in to account modifications
|
||||
* @return {Number} the rounds per shot of this module
|
||||
*/
|
||||
getRoundsPerShot() {
|
||||
return this._getModifiedValue('roundspershot');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DPS for this module, taking in to account modifications
|
||||
* @return {Number} the DPS of this module
|
||||
*/
|
||||
getDps() {
|
||||
// DPS is a synthetic value
|
||||
let damage = this.getDamage();
|
||||
let rpshot = this.roundspershot || 1;
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return damage * rpshot * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the EPS for this module, taking in to account modifications
|
||||
* @return {Number} the EPS of this module
|
||||
*/
|
||||
getEps() {
|
||||
// EPS is a synthetic value
|
||||
let distdraw = this.getDistDraw();
|
||||
// We don't use rpshot here as dist draw is per combined shot
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return distdraw * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HPS for this module, taking in to account modifications
|
||||
* @return {Number} the HPS of this module
|
||||
*/
|
||||
getHps() {
|
||||
// HPS is a synthetic value
|
||||
let heat = this.getThermalLoad();
|
||||
// We don't use rpshot here as dist draw is per combined shot
|
||||
let rof = this.getRoF() || 1;
|
||||
|
||||
return heat * rof;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clip size for this module, taking in to account modifications
|
||||
* @return {Number} the clip size of this module
|
||||
*/
|
||||
getClip() {
|
||||
// Clip size is always rounded up
|
||||
let result = this._getModifiedValue('clip');
|
||||
if (result) { result = Math.ceil(result); }
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ammo size for this module, taking in to account modifications
|
||||
* @return {Number} the ammo size of this module
|
||||
*/
|
||||
getAmmo() {
|
||||
return this._getModifiedValue('ammo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reload time for this module, taking in to account modifications
|
||||
* @return {Number} the reload time of this module
|
||||
*/
|
||||
getReload() {
|
||||
return this._getModifiedValue('reload');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the burst size for this module, taking in to account modifications
|
||||
* @return {Number} the burst size of this module
|
||||
*/
|
||||
getBurst() {
|
||||
return this._getModifiedValue('burst');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the burst rate of fire for this module, taking in to account modifications
|
||||
* @return {Number} the burst rate of fire of this module
|
||||
*/
|
||||
getBurstRoF() {
|
||||
return this._getModifiedValue('burstrof');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate of fire for this module, taking in to account modifications.
|
||||
* The rate of fire is a combination value, and needs to take in to account
|
||||
* bursts of fire.
|
||||
* Firing goes [burst 1] [burst interval] [burst 2] [burst interval] ... [burst n] [interval]
|
||||
* where 'n' is 'burst', 'burst interval' is '1/burstrof' and 'interval' is '1/rof'
|
||||
* @return {Number} the rate of fire for this module
|
||||
*/
|
||||
getRoF() {
|
||||
const burst = this.getBurst() || 1;
|
||||
const burstRoF = this.getBurstRoF() || 1;
|
||||
const intRoF = this._getModifiedValue('rof');
|
||||
|
||||
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the facing limit for this module, taking in to account modifications
|
||||
* @return {Number} the facing limit for this module
|
||||
*/
|
||||
getFacingLimit() {
|
||||
return this._getModifiedValue('facinglimit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hull boost for this module, taking in to account modifications
|
||||
* @return {Number} the hull boost for this module
|
||||
*/
|
||||
getHullBoost() {
|
||||
return this._getModifiedValue('hullboost');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shield reinforcement for this module, taking in to account modifications
|
||||
* @return {Number} the shield reinforcement for this module
|
||||
*/
|
||||
getShieldReinforcement() {
|
||||
return this._getModifiedValue('shieldreinforcement');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the piercing for this module, taking in to account modifications
|
||||
* @return {Number} the piercing for this module
|
||||
*/
|
||||
getPiercing() {
|
||||
return this._getModifiedValue('piercing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bays for this module, taking in to account modifications
|
||||
* @return {Number} the bays for this module
|
||||
*/
|
||||
getBays() {
|
||||
return this._getModifiedValue('bays');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rebuilds per bay for this module, taking in to account modifications
|
||||
* @return {Number} the rebuilds per bay for this module
|
||||
*/
|
||||
getRebuildsPerBay() {
|
||||
return this._getModifiedValue('rebuildsperbay');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the jitter for this module, taking in to account modifications
|
||||
* @return {Number} the jitter for this module
|
||||
*/
|
||||
getJitter() {
|
||||
return this._getModifiedValue('jitter', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the damage distribution for this module, taking in to account modifications
|
||||
* @return {string} the damage distribution for this module
|
||||
*/
|
||||
getDamageDist() {
|
||||
return this.getModValue('damagedist') || this.damagedist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shot speed for this module, taking in to account modifications
|
||||
* @return {string} the shot speed for this module
|
||||
*/
|
||||
getShotSpeed() {
|
||||
if (this.blueprint && (this.blueprint.name === 'Focused' || this.blueprintname === 'Long Range')) {
|
||||
// If the modification is focused or long range then the shot speed
|
||||
// uses the range modifier
|
||||
const rangemod = this.getModValue('range') / 10000;
|
||||
let result = this['shotspeed'];
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
return result * (1 + rangemod);
|
||||
}
|
||||
return this._getModifiedValue('shotspeed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the spinup for this module, taking in to account modifications
|
||||
* @return {string} the spinup for this module
|
||||
*/
|
||||
getSpinup() {
|
||||
return this._getModifiedValue('spinup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time for this module, taking in to account modifications
|
||||
* @return {string} the time for this module
|
||||
*/
|
||||
getTime() {
|
||||
return this._getModifiedValue('time');
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
import Module from './Module';
|
||||
import { BulkheadNames } from './Constants';
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ export default class ModuleSet {
|
||||
this.intClass = {};
|
||||
|
||||
this.bulkheads = shipData.bulkheads.map((b, i) => {
|
||||
return Object.assign({ grp: 'bh', name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
|
||||
return Object.assign(new Module(), { grp: 'bh', id: i, name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
|
||||
});
|
||||
|
||||
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
|
||||
@@ -66,21 +66,28 @@ export default class ModuleSet {
|
||||
* @return {Object} Bulkhead module details
|
||||
*/
|
||||
getBulkhead(index) {
|
||||
return this.bulkheads[index] || null;
|
||||
return this.bulkheads[index] ? new Module({ template: this.bulkheads[index] }) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the modules that areeligible for an internal slot
|
||||
* @param {Object} ship The ship
|
||||
* @param {integer} c The max class module that can be mounted in the slot
|
||||
* @param {Object} eligible) The map of eligible internal groups
|
||||
* @return {object} A map of all eligible modules by group
|
||||
*/
|
||||
getInts(c, eligible) {
|
||||
getInts(ship, c, eligible) {
|
||||
let o = {};
|
||||
for (let key in this.internal) {
|
||||
if (eligible && !eligible[key]) {
|
||||
continue;
|
||||
}
|
||||
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
|
||||
continue;
|
||||
}
|
||||
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
|
||||
continue;
|
||||
}
|
||||
let data = filter(this.internal[key], c, 0, this.mass);
|
||||
if (data.length) { // If group is not empty
|
||||
o[key] = data;
|
||||
@@ -119,11 +126,11 @@ export default class ModuleSet {
|
||||
let pd = this.standard[4][0];
|
||||
|
||||
for (let p of this.standard[4]) {
|
||||
if (p.mass < pd.mass && p.enginecapacity >= boostEnergy) {
|
||||
if (p.mass < pd.mass && p.engcap > boostEnergy) {
|
||||
pd = p;
|
||||
}
|
||||
}
|
||||
return pd;
|
||||
return new Module({ template: pd });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -139,7 +146,7 @@ export default class ModuleSet {
|
||||
th = t;
|
||||
}
|
||||
}
|
||||
return th;
|
||||
return new Module({ template: th });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -155,7 +162,7 @@ export default class ModuleSet {
|
||||
sg = s;
|
||||
}
|
||||
}
|
||||
return sg;
|
||||
return new Module({ template: sg });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -168,10 +175,10 @@ export default class ModuleSet {
|
||||
|
||||
for (let p of this.standard[0]) {
|
||||
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
|
||||
if (p.pGen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pGen > pp.pGen))) {
|
||||
if (p.pgen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pgen > pp.pgen))) {
|
||||
pp = p;
|
||||
}
|
||||
}
|
||||
return pp;
|
||||
return new Module({ template: pp });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
|
||||
import ModuleSet from './ModuleSet';
|
||||
import Module from './Module';
|
||||
import { Ships, Modules } from 'coriolis-data/dist';
|
||||
|
||||
/*
|
||||
* All functions below must return a fresh Module rather than a definition or existing module, as
|
||||
* the resultant object can be altered with modifications.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
@@ -9,9 +14,45 @@ import { Ships, Modules } from 'coriolis-data/dist';
|
||||
* @return {Object} Cargo hatch model
|
||||
*/
|
||||
export function cargoHatch() {
|
||||
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
|
||||
let hatch = new Module();
|
||||
Object.assign(hatch, { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 });
|
||||
return hatch;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the module with the specific group and ID
|
||||
* @param {String} grp Module group (pp - power plant, pl - pulse laser etc)
|
||||
* @param {String} id The module ID
|
||||
* @return {Object} The module or null
|
||||
*/
|
||||
export function findModule(grp, id) {
|
||||
// See if it's a standard module
|
||||
if (Modules.standard[grp]) {
|
||||
let standardmod = Modules.standard[grp].find(e => e.id == id);
|
||||
if (standardmod != null) {
|
||||
return new Module({ template: standardmod });
|
||||
}
|
||||
}
|
||||
|
||||
// See if it's an internal module
|
||||
if (Modules.internal[grp]) {
|
||||
let internalmod = Modules.internal[grp].find(e => e.id == id);
|
||||
if (internalmod != null) {
|
||||
return new Module({ template: internalmod });
|
||||
}
|
||||
}
|
||||
|
||||
// See if it's a hardpoint module
|
||||
if (Modules.hardpoints[grp]) {
|
||||
let hardpointmod = Modules.hardpoints[grp].find(e => e.id == id);
|
||||
if (hardpointmod != null) {
|
||||
return new Module({ template: hardpointmod });
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the standard module type with the specified ID
|
||||
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc)
|
||||
@@ -24,6 +65,9 @@ export function standard(type, id) {
|
||||
}
|
||||
|
||||
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
|
||||
if (s) {
|
||||
s = new Module({ template: s });
|
||||
}
|
||||
return s || null;
|
||||
};
|
||||
|
||||
@@ -37,7 +81,7 @@ export function hardpoints(id) {
|
||||
let group = Modules.hardpoints[n];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
if (group[i].id == id) {
|
||||
return group[i];
|
||||
return new Module({ template: group[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,13 +98,66 @@ export function internal(id) {
|
||||
let group = Modules.internal[n];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
if (group[i].id == id) {
|
||||
return group[i];
|
||||
return new Module({ template: group[i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a standard module based on Class, Rating, Group and/or name.
|
||||
* At least one of Group name or unique module name must be provided
|
||||
*
|
||||
* @param {String} groupName [Optional] Full name or abbreviated name for module group
|
||||
* @param {integer} clss module Class
|
||||
* @param {String} rating module Rating
|
||||
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
|
||||
* @return {Object} The module if found, null if not found
|
||||
*/
|
||||
export function findStandard(groupName, clss, rating, name) {
|
||||
let groups = {};
|
||||
|
||||
if (groupName) {
|
||||
if (Modules.standard[groupName]) {
|
||||
groups[groupName] = Modules.standard[groupName];
|
||||
} else {
|
||||
let grpCode = ModuleNameToGroup[groupName.toLowerCase()];
|
||||
if (grpCode && Modules.standard[grpCode]) {
|
||||
groups[grpCode] = Modules.standard[grpCode];
|
||||
}
|
||||
}
|
||||
} else if (name) {
|
||||
groups = Modules.standard;
|
||||
}
|
||||
|
||||
for (let g in groups) {
|
||||
let group = groups[g];
|
||||
for (let i = 0, l = group.length; i < l; i++) {
|
||||
if (group[i].class == clss && group[i].rating == rating && ((!name && !group[i].name) || group[i].name == name)) {
|
||||
return group[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a standard Module ID based on Class, Rating, Group and/or name.
|
||||
* At least one of Group name or unique module name must be provided
|
||||
*
|
||||
* @param {String} groupName [Optional] Full name or abbreviated name for module group
|
||||
* @param {integer} clss module Class
|
||||
* @param {String} rating Module Rating
|
||||
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
|
||||
* @return {String} The id of the module if found, null if not found
|
||||
*/
|
||||
export function findStandardId(groupName, clss, rating, name) {
|
||||
let i = this.findStandard(groupName, clss, rating, name);
|
||||
return i ? i.id : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an internal module based on Class, Rating, Group and/or name.
|
||||
* At least one ofGroup name or unique module name must be provided
|
||||
@@ -168,6 +265,16 @@ export function findHardpoint(groupName, clss, rating, name, mount, missile) {
|
||||
*/
|
||||
export function findHardpointId(groupName, clss, rating, name, mount, missile) {
|
||||
let h = this.findHardpoint(groupName, clss, rating, name, mount, missile);
|
||||
if (h) {
|
||||
return h.id;
|
||||
}
|
||||
|
||||
// Countermeasures used to be lumped in a single group but have been broken, out. If we have been given a groupName of 'Countermeasure' then
|
||||
// rely on the unique name to find it
|
||||
if (groupName === 'cm' || groupName === 'Countermeasure') {
|
||||
h = this.findHardpoint(null, clss, rating, name, mount, missile);
|
||||
}
|
||||
|
||||
return h ? h.id : 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,40 @@
|
||||
import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from './Ship';
|
||||
import * as ModuleUtils from './ModuleUtils';
|
||||
import * as Utils from '../utils/UtilityFunctions';
|
||||
import LZString from 'lz-string';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
|
||||
/**
|
||||
* Generates ship-loadout JSON Schema standard object
|
||||
* @param {Object} standard model
|
||||
* @return {Object} JSON Schema
|
||||
*/
|
||||
function standardToSchema(standard) {
|
||||
if (standard.m) {
|
||||
let o = {
|
||||
class: standard.m.class,
|
||||
rating: standard.m.rating,
|
||||
enabled: Boolean(standard.enabled),
|
||||
priority: standard.priority + 1
|
||||
};
|
||||
|
||||
if (standard.m.name) {
|
||||
o.name = standard.m.name;
|
||||
}
|
||||
|
||||
if (standard.m.mods && Object.keys(standard.m.mods).length > 0) {
|
||||
o.modifications = standard.m.mods;
|
||||
}
|
||||
|
||||
if (standard.m.blueprint && Object.keys(standard.m.blueprint).length > 0) {
|
||||
o.blueprint = standard.m.blueprint;
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ship-loadout JSON Schema slot object
|
||||
@@ -30,6 +60,13 @@ function slotToSchema(slot) {
|
||||
if (slot.m.missile) {
|
||||
o.missile = slot.m.missile;
|
||||
}
|
||||
if (slot.m.mods && Object.keys(slot.m.mods).length > 0) {
|
||||
o.modifications = slot.m.mods;
|
||||
}
|
||||
if (slot.m.blueprint && Object.keys(slot.m.blueprint).length > 0) {
|
||||
o.blueprint = slot.m.blueprint;
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
return null;
|
||||
@@ -48,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
|
||||
code = ship.toString();
|
||||
|
||||
let data = {
|
||||
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#',
|
||||
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
|
||||
name: buildName,
|
||||
ship: ship.name,
|
||||
references: [{
|
||||
name: 'Coriolis.io',
|
||||
url: `https://coriolis.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
|
||||
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
|
||||
code,
|
||||
shipId: ship.id
|
||||
}],
|
||||
@@ -61,13 +98,13 @@ export function toDetailedBuild(buildName, ship) {
|
||||
standard: {
|
||||
bulkheads: BulkheadNames[ship.bulkheads.m.index],
|
||||
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
|
||||
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 },
|
||||
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
|
||||
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
|
||||
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
|
||||
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
|
||||
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
|
||||
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
|
||||
powerPlant: standardToSchema(standard[0]),
|
||||
thrusters: standardToSchema(standard[1]),
|
||||
frameShiftDrive: standardToSchema(standard[2]),
|
||||
lifeSupport: standardToSchema(standard[3]),
|
||||
powerDistributor: standardToSchema(standard[4]),
|
||||
sensors: standardToSchema(standard[5]),
|
||||
fuelTank: standardToSchema(standard[6])
|
||||
},
|
||||
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
|
||||
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),
|
||||
@@ -92,54 +129,21 @@ export function toDetailedBuild(buildName, ship) {
|
||||
*/
|
||||
export function fromDetailedBuild(detailedBuild) {
|
||||
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
|
||||
|
||||
if (!shipId) {
|
||||
throw 'No such ship: ' + detailedBuild.ship;
|
||||
}
|
||||
|
||||
let comps = detailedBuild.components;
|
||||
let stn = comps.standard;
|
||||
let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0];
|
||||
let enabled = [stn.cargoHatch && stn.cargoHatch.enabled !== undefined ? stn.cargoHatch.enabled : true];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
let bulkheads = ModuleUtils.bulkheadIndex(stn.bulkheads);
|
||||
|
||||
if (bulkheads < 0) {
|
||||
throw 'Invalid bulkheads: ' + stn.bulkheads;
|
||||
if (!detailedBuild.references[0] || !detailedBuild.references[0].code) {
|
||||
throw 'Missing reference code';
|
||||
}
|
||||
|
||||
let standard = STANDARD.map((c) => {
|
||||
if (!stn[c].class || !stn[c].rating) {
|
||||
throw 'Invalid value for ' + c;
|
||||
}
|
||||
priorities.push(stn[c].priority === undefined ? 0 : stn[c].priority - 1);
|
||||
enabled.push(stn[c].enabled === undefined ? true : stn[c].enabled);
|
||||
return stn[c].class + stn[c].rating;
|
||||
});
|
||||
|
||||
let internal = comps.internal.map(c => c ? ModuleUtils.findInternalId(c.group, c.class, c.rating, c.name) : 0);
|
||||
|
||||
let hardpoints = comps.hardpoints
|
||||
.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0)
|
||||
.concat(comps.utility.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0));
|
||||
|
||||
// The ordering of these arrays must match the order in which they are read in Ship.buildWith
|
||||
priorities = priorities.concat(
|
||||
comps.hardpoints.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
|
||||
comps.utility.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
|
||||
comps.internal.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1)
|
||||
);
|
||||
enabled = enabled.concat(
|
||||
comps.hardpoints.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
|
||||
comps.utility.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
|
||||
comps.internal.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1)
|
||||
);
|
||||
|
||||
ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled);
|
||||
ship.buildFrom(detailedBuild.references[0].code);
|
||||
|
||||
return ship;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of ship-loadout JSON Schema object for export
|
||||
@@ -178,7 +182,7 @@ export function fromComparison(name, builds, facets, predicate, desc) {
|
||||
f: facets,
|
||||
p: predicate,
|
||||
d: desc ? 1 : 0
|
||||
})).replace(/\//g, '-');
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -187,5 +191,5 @@ export function fromComparison(name, builds, facets, predicate, desc) {
|
||||
* @return {Object} Comparison data object
|
||||
*/
|
||||
export function toComparison(code) {
|
||||
return JSON.parse(LZString.decompressFromBase64(code.replace(/-/g, '/')));
|
||||
return JSON.parse(LZString.decompressFromBase64(Utils.fromUrlSafe(code)));
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
|
||||
|
||||
if (shielded) {
|
||||
ship.internal.some(function(slot) {
|
||||
if (canMount(slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, true);
|
||||
return true;
|
||||
@@ -31,18 +31,38 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
|
||||
* @param {Object} standardOpts [Optional] Standard module optional overrides
|
||||
*/
|
||||
export function trader(ship, shielded, standardOpts) {
|
||||
let sg = shielded ? ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) : null;
|
||||
let usedSlots = [],
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
// Shield generator if required
|
||||
if (shielded) {
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < shieldInternals.length; i++) {
|
||||
if (canMount(ship, shieldInternals[i], 'sg')) {
|
||||
ship.use(shieldInternals[i], sg);
|
||||
usedSlots.push(shieldInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the empty internals with cargo racks
|
||||
for (let i = ship.internal.length; i--;) {
|
||||
let slot = ship.internal[i];
|
||||
if (sg && canMount(slot, 'sg', sg.class)) {
|
||||
ship.use(slot, sg);
|
||||
sg = null;
|
||||
} else {
|
||||
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
}
|
||||
|
||||
// Empty the hardpoints
|
||||
for (let s of ship.hardpoints) {
|
||||
ship.use(s, null);
|
||||
}
|
||||
|
||||
ship.useLightestStandard(standardOpts);
|
||||
}
|
||||
|
||||
@@ -53,53 +73,102 @@ export function trader(ship, shielded, standardOpts) {
|
||||
*/
|
||||
export function explorer(ship, planetary) {
|
||||
let standardOpts = { ppRating: 'A' },
|
||||
intLength = ship.internal.length,
|
||||
heatSinkCount = 2, // Fit 2 heat sinks if possible
|
||||
afmUnitCount = 2, // Fit 2 AFM Units if possible
|
||||
shieldNext = planetary,
|
||||
usedSlots = [],
|
||||
sgSlot,
|
||||
fuelScoopSlot,
|
||||
pvhSlot,
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
if (!planetary) { // Non-planetary explorers don't really need to boost
|
||||
standardOpts.pd = '1D';
|
||||
}
|
||||
|
||||
ship.setSlotEnabled(ship.cargoHatch, false)
|
||||
.use(ship.internal[--intLength], ModuleUtils.internal('2f')); // Advanced Discovery Scanner
|
||||
// Cargo hatch can be disabled
|
||||
ship.setSlotEnabled(ship.cargoHatch, false);
|
||||
|
||||
if (!planetary || intLength > 3) { // Don't mount a DDS on planetary explorer ships too small for both a PVH and DDS
|
||||
ship.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner
|
||||
// Advanced Discovery Scanner - class 1 or higher
|
||||
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sc)
|
||||
.sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < intLength; i++) {
|
||||
let slot = ship.internal[i];
|
||||
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
|
||||
// Fit best possible Fuel Scoop
|
||||
if (!fuelScoopSlot && canMount(slot, 'fs')) {
|
||||
fuelScoopSlot = slot;
|
||||
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, true);
|
||||
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
|
||||
} else if (!sgSlot && shieldNext && canMount(slot, 'sg', sg.class) && !canMount(nextSlot, 'sg', sg.class)) {
|
||||
sgSlot = slot;
|
||||
shieldNext = false;
|
||||
ship.use(slot, sg);
|
||||
ship.setSlotEnabled(slot, true);
|
||||
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
|
||||
} else if (planetary && !pvhSlot && canMount(slot, 'pv') && (shieldNext || !canMount(nextSlot, 'pv', 2))) {
|
||||
pvhSlot = slot;
|
||||
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
|
||||
ship.setSlotEnabled(slot, false); // Disabled power for PVH
|
||||
shieldNext = !sgSlot;
|
||||
} else if (afmUnitCount > 0 && canMount(slot, 'am')) {
|
||||
afmUnitCount--;
|
||||
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
|
||||
shieldNext = !sgSlot;
|
||||
} else {
|
||||
ship.use(slot, null);
|
||||
if (planetary) {
|
||||
// Planetary Vehicle Hangar - class 2 or higher
|
||||
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
|
||||
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pv)
|
||||
.sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed Surface Scanner
|
||||
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sc)
|
||||
.sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Fuel scoop - best possible
|
||||
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
|
||||
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.fs)
|
||||
.sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// AFMUs - fill as they are 0-weight
|
||||
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
|
||||
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pc)
|
||||
.sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +182,9 @@ export function explorer(ship, planetary) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sgSlot) {
|
||||
if (sgSlot && fuelScoopSlot) {
|
||||
// The SG and Fuel scoop to not need to be powered at the same time
|
||||
if (sgSlot.m.power > fuelScoopSlot.m.power) { // The Shield generator uses the most power
|
||||
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
|
||||
ship.setSlotEnabled(fuelScoopSlot, false);
|
||||
} else { // The Fuel scoop uses the most power
|
||||
ship.setSlotEnabled(sgSlot, false);
|
||||
@@ -124,3 +193,104 @@ export function explorer(ship, planetary) {
|
||||
|
||||
ship.useLightestStandard(standardOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Miner Role
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
export function miner(ship, shielded) {
|
||||
let standardOpts = { ppRating: 'A' },
|
||||
miningLaserCount = 2,
|
||||
usedSlots = [],
|
||||
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||
|
||||
// Cargo hatch should be enabled
|
||||
ship.setSlotEnabled(ship.cargoHatch, true);
|
||||
|
||||
// Largest possible refinery
|
||||
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
|
||||
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.rf)
|
||||
.sort((a,b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Prospector limpet controller - 3A if possible
|
||||
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
|
||||
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.pc)
|
||||
.sort((a,b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator if required
|
||||
if (shielded) {
|
||||
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||
.filter(a => a.maxClass >= sg.class)
|
||||
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < shieldInternals.length; i++) {
|
||||
if (canMount(ship, shieldInternals[i], 'sg')) {
|
||||
ship.use(shieldInternals[i], sg);
|
||||
usedSlots.push(shieldInternals[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collector limpet controller if there are enough internals left
|
||||
let collectorLimpetsRequired = Math.max(ship.internal.filter(a => (!a.eligible) || a.eligible.cr).length - 6, 0);
|
||||
if (collectorLimpetsRequired > 0) {
|
||||
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
|
||||
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
|
||||
.filter(a => (!a.eligible) || a.eligible.cc)
|
||||
.sort((a,b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
|
||||
for (let i = 0; i < collectorInternals.length && collectorLimpetsRequired > 0; i++) {
|
||||
if (canMount(ship, collectorInternals[i], 'cc')) {
|
||||
// Collector only has odd classes
|
||||
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
|
||||
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'A'));
|
||||
usedSlots.push(collectorInternals[i]);
|
||||
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dual mining lasers of highest possible class; remove anything else
|
||||
const miningLaserOrder = [2, 3, 4, 1, 0];
|
||||
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a,b) {
|
||||
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--;
|
||||
} else {
|
||||
ship.use(s, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the empty internals with cargo racks
|
||||
for (let i = ship.internal.length; 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.useLightestStandard(standardOpts);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ 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_OUTFITTING_TAB = 'outfittingTab';
|
||||
const LS_KEY_INSURANCE = 'insurance';
|
||||
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
|
||||
const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
|
||||
const LS_KEY_STATE = 'state';
|
||||
const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
||||
|
||||
let LS;
|
||||
|
||||
@@ -81,6 +83,7 @@ export class Persist extends EventEmitter {
|
||||
LS = null;
|
||||
}
|
||||
|
||||
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let insurance = _getString(LS_KEY_INSURANCE);
|
||||
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
|
||||
@@ -96,9 +99,11 @@ export class Persist extends EventEmitter {
|
||||
this.builds = buildJson && typeof buildJson == 'object' ? buildJson : {};
|
||||
this.comparisons = comparisonJson && typeof comparisonJson == 'object' ? comparisonJson : {};
|
||||
this.costTab = _getString(LS_KEY_COST_TAB);
|
||||
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
|
||||
this.state = _get(LS_KEY_STATE);
|
||||
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
|
||||
this.tooltipsEnabled = tips === null ? true : tips;
|
||||
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
|
||||
|
||||
if (LS) {
|
||||
window.addEventListener('storage', this.onStorageChange);
|
||||
@@ -143,6 +148,10 @@ export class Persist extends EventEmitter {
|
||||
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
this.emit('tooltips', this.tooltipsEnabled);
|
||||
break;
|
||||
case LS_KEY_MODULE_RESISTANCES:
|
||||
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
|
||||
this.emit('moduleresistances', this.moduleResistancesEnabled);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// On JSON.Parse Error - don't sync or do anything
|
||||
@@ -183,6 +192,21 @@ export class Persist extends EventEmitter {
|
||||
return this.tooltipsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show module resistances setting
|
||||
* @param {boolean} show Optional - update setting
|
||||
* @return {boolean} True if module resistances should be shown
|
||||
*/
|
||||
showModuleResistances(show) {
|
||||
if (show !== undefined) {
|
||||
this.moduleResistancesEnabled = !!show;
|
||||
_put(LS_KEY_MODULE_RESISTANCES, this.moduleResistancesEnabled);
|
||||
this.emit('moduleresistances', this.moduleResistancesEnabled);
|
||||
}
|
||||
|
||||
return this.moduleResistancesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a ship build in local storage.
|
||||
*
|
||||
@@ -450,6 +474,22 @@ export class Persist extends EventEmitter {
|
||||
return this.costTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist selected outfitting tab
|
||||
* @param {string} tabName Cost tab name
|
||||
*/
|
||||
setOutfittingTab(tabName) {
|
||||
this.outfittingTab = tabName;
|
||||
_put(LS_KEY_OUTFITTING_TAB, tabName);
|
||||
}
|
||||
/**
|
||||
* Get the current outfitting tab
|
||||
* @return {string} the current outfitting tab
|
||||
*/
|
||||
getOutfittingTab() {
|
||||
return this.outfittingTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last router state from local storage
|
||||
* @return {Object} state State object containing state name and params
|
||||
|
||||
229
src/app/utils/BlueprintFunctions.js
Normal file
229
src/app/utils/BlueprintFunctions.js
Normal file
@@ -0,0 +1,229 @@
|
||||
import React from 'react';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
|
||||
/**
|
||||
* Generate a tooltip with details of a blueprint's effects
|
||||
* @param {Object} translate The translate object
|
||||
* @param {Object} blueprint The blueprint at the required grade
|
||||
* @param {Array} engineers The engineers supplying this blueprint
|
||||
* @param {string} grp The group of the module
|
||||
* @param {Object} m The module to compare with
|
||||
* @returns {Object} The react components
|
||||
*/
|
||||
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
const effects = [];
|
||||
for (const feature in blueprint.features) {
|
||||
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (!featureDef.hidden) {
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let lowerBound = blueprint.features[feature][0];
|
||||
let upperBound = blueprint.features[feature][1];
|
||||
if (featureDef.type === 'percentage') {
|
||||
lowerBound = Math.round(lowerBound * 1000) / 10;
|
||||
upperBound = Math.round(upperBound * 1000) / 10;
|
||||
}
|
||||
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
|
||||
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
|
||||
if (m) {
|
||||
// We have a module - add in the current value
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
} else {
|
||||
// We do not have a module, no value
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
|
||||
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m) {
|
||||
// Because we have a module add in any benefits that aren't part of the primary blueprint
|
||||
for (const feature in m.mods) {
|
||||
if (!blueprint.features[feature]) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
let symbol = '';
|
||||
if (feature === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (featureDef.type === 'percentage') {
|
||||
symbol = '%';
|
||||
}
|
||||
let current = m.getModValue(feature);
|
||||
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
|
||||
current = Math.round(current / 10) / 10;
|
||||
} else if (featureDef.type === 'numeric') {
|
||||
current /= 100;
|
||||
}
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
effects.push(
|
||||
<tr key={feature}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </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 blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
|
||||
if (module) {
|
||||
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
|
||||
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
|
||||
for (const grade in blueprint.grades) {
|
||||
for (const feature in blueprint.grades[grade].features) {
|
||||
if (feature === 'explres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
|
||||
}
|
||||
if (feature === 'kinres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
|
||||
}
|
||||
if (feature === 'thermres') {
|
||||
blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
|
||||
blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (module.grp === 'sb') {
|
||||
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
|
||||
// being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
|
||||
// accurate as per the information in Elite
|
||||
for (const grade in blueprint.grades) {
|
||||
for (const feature in blueprint.grades[grade].features) {
|
||||
if (feature === 'shieldboost') {
|
||||
blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
|
||||
blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return blueprint;
|
||||
}
|
||||
444
src/app/utils/CompanionApiUtils.js
Normal file
444
src/app/utils/CompanionApiUtils.js
Normal file
@@ -0,0 +1,444 @@
|
||||
import React from 'react';
|
||||
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
|
||||
import Module from '../shipyard/Module';
|
||||
import Ship from '../shipyard/Ship';
|
||||
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 = {
|
||||
'Adder': 'adder',
|
||||
'Anaconda': 'anaconda',
|
||||
'Asp': 'asp',
|
||||
'Asp_Scout': 'asp_scout',
|
||||
'BelugaLiner': 'beluga',
|
||||
'CobraMkIII': 'cobra_mk_iii',
|
||||
'CobraMkIV': 'cobra_mk_iv',
|
||||
'Cutter': 'imperial_cutter',
|
||||
'DiamondBackXL': 'diamondback_explorer',
|
||||
'DiamondBack': 'diamondback',
|
||||
'Dolphin': 'dolphin',
|
||||
'Eagle': 'eagle',
|
||||
'Empire_Courier': 'imperial_courier',
|
||||
'Empire_Eagle': 'imperial_eagle',
|
||||
'Empire_Trader': 'imperial_clipper',
|
||||
'Federation_Corvette': 'federal_corvette',
|
||||
'Federation_Dropship': 'federal_dropship',
|
||||
'Federation_Dropship_MkII': 'federal_assault_ship',
|
||||
'Federation_Gunship': 'federal_gunship',
|
||||
'FerDeLance': 'fer_de_lance',
|
||||
'Hauler': 'hauler',
|
||||
'Independant_Trader': 'keelback',
|
||||
'Orca': 'orca',
|
||||
'Python': 'python',
|
||||
'SideWinder': 'sidewinder',
|
||||
'Type6': 'type_6_transporter',
|
||||
'Type7': 'type_7_transport',
|
||||
'Type9': 'type_9_heavy',
|
||||
'Viper': 'viper',
|
||||
'Viper_MkIV': 'viper_mk_iv',
|
||||
'Vulture': 'vulture'
|
||||
};
|
||||
|
||||
// Mapping from hardpoint class to name in companion API
|
||||
const HARDPOINT_NUM_TO_CLASS = {
|
||||
0: 'Tiny',
|
||||
1: 'Small',
|
||||
2: 'Medium',
|
||||
3: 'Large',
|
||||
4: 'Huge'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a module given its ED ID
|
||||
* @param {Integer} edId the Elite ID of the module
|
||||
* @return {Module} the module
|
||||
*/
|
||||
function _moduleFromEdId(edId) {
|
||||
if (!edId) return null;
|
||||
|
||||
// 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].edID === edId) {
|
||||
// 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].edID === edId) {
|
||||
// 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].edID === edId) {
|
||||
// Found it
|
||||
return new Module({ template: Modules.internal[grp][i] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the model of a ship given its ED name
|
||||
* @param {string} edName the Elite name of the ship
|
||||
* @return {string} the Coriolis model of the ship
|
||||
*/
|
||||
function _shipModelFromEDName(edName) {
|
||||
return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a ship's model from the companion API JSON
|
||||
* @param {object} json the companion API JSON
|
||||
* @return {string} the Coriolis model of the ship
|
||||
*/
|
||||
export function shipModelFromJson(json) {
|
||||
return _shipModelFromEDName(json.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a ship from the companion API JSON
|
||||
* @param {object} json the companion API JSON
|
||||
* @return {Ship} the built ship
|
||||
*/
|
||||
export function shipFromJson(json) {
|
||||
// Start off building a basic ship
|
||||
const shipModel = shipModelFromJson(json);
|
||||
if (!shipModel) {
|
||||
throw 'No such ship found: "' + json.name + '"';
|
||||
}
|
||||
const shipTemplate = Ships[shipModel];
|
||||
|
||||
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||
ship.buildWith(null);
|
||||
|
||||
// Set the cargo hatch
|
||||
if (json.modules.CargoHatch) {
|
||||
ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true;
|
||||
ship.cargoHatch.priority = json.modules.CargoHatch.module.priority;
|
||||
} else {
|
||||
// We don't have any information on it so guess it's priority 5 and disabled
|
||||
ship.cargoHatch.enabled = false;
|
||||
ship.cargoHatch.priority = 4;
|
||||
}
|
||||
|
||||
// Add the bulkheads
|
||||
const armourJson = json.modules.Armour.module;
|
||||
if (armourJson.name.endsWith('_Armour_Grade1')) {
|
||||
ship.useBulkhead(0, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade2')) {
|
||||
ship.useBulkhead(1, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Grade3')) {
|
||||
ship.useBulkhead(2, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Mirrored')) {
|
||||
ship.useBulkhead(3, true);
|
||||
} else if (armourJson.name.endsWith('_Armour_Reactive')) {
|
||||
ship.useBulkhead(4, true);
|
||||
} else {
|
||||
throw 'Unknown bulkheads "' + armourJson.name + '"';
|
||||
}
|
||||
ship.bulkheads.enabled = true;
|
||||
if (armourJson.modifiers) _addModifications(ship.bulkheads.m, armourJson.modifiers, armourJson.recipeName, armourJson.recipeLevel);
|
||||
|
||||
// Add the standard modules
|
||||
// Power plant
|
||||
const powerplantJson = json.modules.PowerPlant.module;
|
||||
const powerplant = _moduleFromEdId(powerplantJson.id);
|
||||
if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers, powerplantJson.recipeName, powerplantJson.recipeLevel);
|
||||
ship.use(ship.standard[0], powerplant, true);
|
||||
ship.standard[0].enabled = powerplantJson.on === true;
|
||||
ship.standard[0].priority = powerplantJson.priority;
|
||||
|
||||
// Thrusters
|
||||
const thrustersJson = json.modules.MainEngines.module;
|
||||
const thrusters = _moduleFromEdId(thrustersJson.id);
|
||||
if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers, thrustersJson.recipeName, thrustersJson.recipeLevel);
|
||||
ship.use(ship.standard[1], thrusters, true);
|
||||
ship.standard[1].enabled = thrustersJson.on === true;
|
||||
ship.standard[1].priority = thrustersJson.priority;
|
||||
|
||||
// FSD
|
||||
const frameshiftdriveJson = json.modules.FrameShiftDrive.module;
|
||||
const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id);
|
||||
if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers, frameshiftdriveJson.recipeName, frameshiftdriveJson.recipeLevel);
|
||||
ship.use(ship.standard[2], frameshiftdrive, true);
|
||||
ship.standard[2].enabled = frameshiftdriveJson.on === true;
|
||||
ship.standard[2].priority = frameshiftdriveJson.priority;
|
||||
|
||||
// Life support
|
||||
const lifesupportJson = json.modules.LifeSupport.module;
|
||||
const lifesupport = _moduleFromEdId(lifesupportJson.id);
|
||||
if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers, lifesupportJson.recipeName, lifesupportJson.recipeLevel);
|
||||
ship.use(ship.standard[3], lifesupport, true);
|
||||
ship.standard[3].enabled = lifesupportJson.on === true;
|
||||
ship.standard[3].priority = lifesupportJson.priority;
|
||||
|
||||
// Power distributor
|
||||
const powerdistributorJson = json.modules.PowerDistributor.module;
|
||||
const powerdistributor = _moduleFromEdId(powerdistributorJson.id);
|
||||
if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers, powerdistributorJson.recipeName, powerdistributorJson.recipeLevel);
|
||||
ship.use(ship.standard[4], powerdistributor, true);
|
||||
ship.standard[4].enabled = powerdistributorJson.on === true;
|
||||
ship.standard[4].priority = powerdistributorJson.priority;
|
||||
|
||||
// Sensors
|
||||
const sensorsJson = json.modules.Radar.module;
|
||||
const sensors = _moduleFromEdId(sensorsJson.id);
|
||||
if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers, sensorsJson.recipeName, sensorsJson.recipeLevel);
|
||||
ship.use(ship.standard[5], sensors, true);
|
||||
ship.standard[5].enabled = sensorsJson.on === true;
|
||||
ship.standard[5].priority = sensorsJson.priority;
|
||||
|
||||
// Fuel tank
|
||||
const fueltankJson = json.modules.FuelTank.module;
|
||||
const fueltank = _moduleFromEdId(fueltankJson.id);
|
||||
ship.use(ship.standard[6], fueltank, true);
|
||||
ship.standard[6].enabled = true;
|
||||
ship.standard[6].priority = 0;
|
||||
|
||||
// Add hardpoints
|
||||
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[hardpointName];
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const hardpointJson = hardpointSlot.module;
|
||||
const hardpoint = _moduleFromEdId(hardpointJson.id);
|
||||
if (hardpointJson.modifiers) _addModifications(hardpoint, hardpointJson.modifiers, hardpointJson.recipeName, hardpointJson.recipeLevel);
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true;
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority;
|
||||
}
|
||||
hardpointArrayNum++;
|
||||
}
|
||||
|
||||
// Add internal compartments
|
||||
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[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[internalName]) {
|
||||
internalSlot = json.modules[internalName];
|
||||
break;
|
||||
}
|
||||
}
|
||||
internalSlotNum++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else if (!internalSlot.module) {
|
||||
// No module
|
||||
} else {
|
||||
const internalJson = internalSlot.module;
|
||||
const internal = _moduleFromEdId(internalJson.id);
|
||||
if (internalJson.modifiers) _addModifications(internal, internalJson.modifiers, internalJson.recipeName, internalJson.recipeLevel);
|
||||
ship.use(ship.internal[i], internal, true);
|
||||
ship.internal[i].enabled = internalJson.on === true;
|
||||
ship.internal[i].priority = internalJson.priority;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
*/
|
||||
function _addModifications(module, modifiers, blueprint, grade) {
|
||||
if (!modifiers || !modifiers.modifiers) return;
|
||||
|
||||
let special;
|
||||
for (const i in modifiers.modifiers) {
|
||||
// Some special modifications
|
||||
if (modifiers.modifiers[i].name === 'mod_weapon_clip_size_override') {
|
||||
// This is a numeric addition to the clip size, but we need to work it out in terms of being a percentage so
|
||||
// that it works the same as other modifications
|
||||
const origClip = module.clip || 1;
|
||||
module.setModValue('clip', ((modifiers.modifiers[i].value - origClip) / origClip) * 10000);
|
||||
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_size') {
|
||||
// This is an absolute number that acts as an override
|
||||
module.setModValue('burst', modifiers.modifiers[i].value * 100);
|
||||
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_rof') {
|
||||
// This is an absolute number that acts as an override
|
||||
module.setModValue('burstrof', modifiers.modifiers[i].value * 100);
|
||||
} else if (modifiers.modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
|
||||
// Obtain the falloff value directly from the range
|
||||
module.setModValue('fallofffromrange', 1);
|
||||
} else if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
|
||||
// We don't add special effects directly, but keep a note of them so they can be added when fetching values
|
||||
special = Modifications.specials[modifiers.modifiers[i].name];
|
||||
} else {
|
||||
// Look up the modifiers to find what we need to do
|
||||
const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name];
|
||||
const value = modifiers.modifiers[i].value;
|
||||
|
||||
// Carry out the required changes
|
||||
for (const action in modifierActions) {
|
||||
if (isNaN(modifierActions[action])) {
|
||||
module.setModValue(action, modifierActions[action]);
|
||||
} else {
|
||||
const actionValue = modifierActions[action] * value;
|
||||
let mod = module.getModValue(action) / 10000;
|
||||
if (!mod) {
|
||||
mod = 0;
|
||||
}
|
||||
module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to fix up a few items
|
||||
|
||||
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
|
||||
// being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification
|
||||
// is incorrect so we fix it
|
||||
if (module.grp === 'sb' && module.getModValue('shieldboost')) {
|
||||
const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost') / 10000);
|
||||
module.setModValue('shieldboost', alteredBoost * 10000 / module.shieldboost);
|
||||
}
|
||||
|
||||
// Shield booster resistance is actually a damage modifier, so needs to be inverted.
|
||||
if (module.grp === 'sb') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Shield generator resistance is actually a damage modifier, so needs to be inverted.
|
||||
// In addition, the modification is based off the inherent resistance of the module
|
||||
if (ModuleUtils.isShieldGenerator(module.grp)) {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted.
|
||||
// In addition, the modification is based off the inherent resistance of the module
|
||||
if (module.grp === 'hr') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Bulkhead resistance is actually a damage modifier, so needs to be inverted.
|
||||
// In addition, the modification is based off the inherent resistance of the module
|
||||
if (module.grp == 'bh') {
|
||||
if (module.getModValue('explres')) {
|
||||
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
|
||||
}
|
||||
if (module.getModValue('kinres')) {
|
||||
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
|
||||
}
|
||||
if (module.getModValue('thermres')) {
|
||||
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Bulkhead boost is based off the inherent boost of the module
|
||||
if (module.grp == 'bh') {
|
||||
const alteredBoost = (1 + module.hullboost) * (1 + module.getModValue('hullboost') / 10000) - 1;
|
||||
module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 10000);
|
||||
}
|
||||
|
||||
// Jitter is an absolute number, so we need to divide it by 100
|
||||
if (module.getModValue('jitter')) {
|
||||
module.setModValue('jitter', module.getModValue('jitter') / 100);
|
||||
}
|
||||
|
||||
// FD uses interval between bursts internally, so we need to translate this to a real rate of fire
|
||||
if (module.getModValue('rof')) {
|
||||
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
|
||||
}
|
||||
|
||||
// Clip size is rounded up so that the result is a whole number
|
||||
if (module.getModValue('clip')) {
|
||||
const individual = 1 / (module.clip || 1);
|
||||
module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,27 @@
|
||||
import request from 'superagent';
|
||||
|
||||
const SHORTEN_API = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
/**
|
||||
* Shorten a URL using Google's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
export default function shortenUrl(url, success, error) {
|
||||
function shortenUrlGoogle(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API + window.CORIOLIS_GAPI_KEY)
|
||||
request.post(SHORTEN_API_GOOGLE + window.CORIOLIS_GAPI_KEY)
|
||||
.send({ longUrl: url })
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
@@ -27,3 +37,30 @@ export default function shortenUrl(url, success, error) {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const SHORTEN_API_EDDP = 'https://eddp.co/u';
|
||||
/**
|
||||
* Shorten a URL using EDDP's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
function shortenUrlEddp(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API_EDDP)
|
||||
.send(url)
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
error('Bad Request');
|
||||
} else {
|
||||
success(response.header['location']);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
error(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import { isShieldGenerator } from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import { Infinite } from '../components/SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
|
||||
/**
|
||||
* Determine if a slot can mount a module of a particular class and group
|
||||
* Determine if a slot on a ship can mount a module of a particular class and group
|
||||
* @param {Object} ship Ship object
|
||||
* @param {Object} slot Slot object
|
||||
* @param {String} group Module group/type abbrivation/code
|
||||
* @param {Integer} clazz [Optional] Module Class/Size
|
||||
* @return {Boolean} True if the slot can mount the module
|
||||
*/
|
||||
export function canMount(slot, group, clazz) {
|
||||
if (slot && (!slot.eligible || slot.eligible[group]) && (clazz === undefined || slot.maxClass >= clazz)) {
|
||||
export function canMount(ship, slot, group, clazz) {
|
||||
if (slot &&
|
||||
(!slot.eligible || slot.eligible[group]) &&
|
||||
(group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) &&
|
||||
(group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) &&
|
||||
(clazz === undefined || slot.maxClass >= clazz)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -87,69 +93,6 @@ export function slotComparator(translate, propComparator, desc) {
|
||||
};
|
||||
}
|
||||
|
||||
const PROP_BLACKLIST = {
|
||||
eddbID: 1,
|
||||
edID: 1,
|
||||
id: 1,
|
||||
index: 1,
|
||||
'class': 1,
|
||||
rating: 1,
|
||||
maxfuel: 1,
|
||||
fuelmul: 1,
|
||||
fuelpower: 1,
|
||||
optmass: 1,
|
||||
maxmass: 1,
|
||||
minmass: 1,
|
||||
passive: 1,
|
||||
thermload: 1,
|
||||
ammocost: 1,
|
||||
activepower: 1,
|
||||
cooldown: 1,
|
||||
chargeup: 1,
|
||||
optmul: 1,
|
||||
minmul: 1,
|
||||
maxmul: 1,
|
||||
ssdam: 1,
|
||||
mjdps: 1,
|
||||
mjeps: 1,
|
||||
M: 1,
|
||||
P: 1,
|
||||
mass: 1,
|
||||
cost: 1,
|
||||
recover: 1,
|
||||
weaponcapacity: 1,
|
||||
weaponrecharge: 1,
|
||||
enginecapacity: 1,
|
||||
enginerecharge: 1,
|
||||
systemcapacity: 1,
|
||||
systemrecharge: 1
|
||||
};
|
||||
|
||||
const TERM_LOOKUP = {
|
||||
pGen: 'power',
|
||||
armouradd: 'armour',
|
||||
shieldmul: 'multiplier',
|
||||
rof: 'ROF',
|
||||
dps: 'DPS'
|
||||
};
|
||||
|
||||
const FORMAT_LOOKUP = {
|
||||
time: 'time',
|
||||
shieldmul: 'rPct'
|
||||
};
|
||||
|
||||
const UNIT_LOOKUP = {
|
||||
fuel: 'T',
|
||||
cargo: 'T',
|
||||
rate: 'kgs',
|
||||
range: 'km',
|
||||
recharge: 'MJ',
|
||||
rangeLS: 'Ls',
|
||||
power: 'MJ',
|
||||
pGen: 'MJ',
|
||||
rof: 'ps'
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the appropriate class based on diff value
|
||||
* @param {Number} a Potential Module (cannot be null)
|
||||
@@ -175,7 +118,7 @@ function diffClass(a, b, negative) {
|
||||
*/
|
||||
function diff(format, mVal, mmVal) {
|
||||
if (mVal == Infinity) {
|
||||
return <Infinite/>;
|
||||
return '∞';
|
||||
} else {
|
||||
let diff = mVal - mmVal;
|
||||
if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
|
||||
@@ -197,68 +140,96 @@ function diff(format, mVal, mmVal) {
|
||||
* @return {React.Component} Component to be rendered
|
||||
*/
|
||||
export function diffDetails(language, m, mm) {
|
||||
mm = mm || {};
|
||||
let { formats, translate, units } = language;
|
||||
let propDiffs = [];
|
||||
let mMass = m.mass || 0;
|
||||
let mmMass = mm.mass || 0;
|
||||
let massDiff = mMass - mmMass;
|
||||
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
|
||||
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
|
||||
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb';
|
||||
|
||||
propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{m.cost ? Math.round(m.cost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
|
||||
propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.mass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
||||
// Module-specific items
|
||||
|
||||
for (let p in m) {
|
||||
if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {
|
||||
let mVal = m[p] === null ? Infinity : m[p];
|
||||
let mmVal = mm[p] === null ? Infinity : mm[p];
|
||||
let format = formats[FORMAT_LOOKUP[p]] || formats.round;
|
||||
propDiffs.push(<div key={p}>
|
||||
{`${translate(TERM_LOOKUP[p] || p)}: `}
|
||||
<span className={diffClass(mVal, mmVal, p == 'power')}>{diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]}</span>
|
||||
</div>);
|
||||
}
|
||||
if (m.grp === 'pp') {
|
||||
let mPowerGeneration = m.pgen || 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>);
|
||||
} else {
|
||||
let mPowerUsage = m.power || 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>);
|
||||
}
|
||||
|
||||
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
|
||||
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>);
|
||||
|
||||
let mAffectsShield = ModuleUtils.isShieldGenerator(m.grp) || m.grp == 'sb';
|
||||
let mmAffectsShield = mm ? ModuleUtils.isShieldGenerator(m.grp) || mm.grp == 'sb' : false;
|
||||
if (mAffectsShield || mmAffectsShield) {
|
||||
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
|
||||
let newShield = 0;
|
||||
|
||||
if (mAffectsShield) {
|
||||
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
|
||||
newShield = this.calcShieldStrengthWith(null, m.shieldmul - (mm.shieldmul || 0));
|
||||
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
|
||||
} else {
|
||||
newShield = this.calcShieldStrengthWith(m);
|
||||
}
|
||||
} else {
|
||||
// Old module must be a shield booster
|
||||
newShield = this.calcShieldStrengthWith(null, -mm.getShieldBoost());
|
||||
}
|
||||
|
||||
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
|
||||
|
||||
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
|
||||
}
|
||||
|
||||
if (m.grp === 'mrp') {
|
||||
let mProtection = m.protection;
|
||||
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>);
|
||||
}
|
||||
}
|
||||
|
||||
if (m.grp === 'hr') {
|
||||
let mHullReinforcement = m.hullreinforcement;
|
||||
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>);
|
||||
}
|
||||
|
||||
if (m.grp == 'pd') {
|
||||
propDiffs.push(<div key='wep'>
|
||||
{`${translate('WEP')}: `}
|
||||
<span className={diffClass(m.weaponcapacity, mm.weaponcapacity)}>{m.weaponcapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.wepcap, mm.getWeaponsCapacity())}>{m.wepcap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.weaponrecharge, mm.weaponrecharge)}>{m.weaponrecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.weprate, mm.getWeaponsRechargeRate())}>{m.weprate}{units.MW}</span>
|
||||
</div>);
|
||||
propDiffs.push(<div key='sys'>
|
||||
{`${translate('SYS')}: `}
|
||||
<span className={diffClass(m.systemcapacity, mm.systemcapacity)}>{m.systemcapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.syscap, mm.getSystemsCapacity())}>{m.syscap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.systemrecharge, mm.systemrecharge)}>{m.systemrecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.sysrate, mm.getSystemsRechargeRate())}>{m.sysrate}{units.MW}</span>
|
||||
</div>);
|
||||
propDiffs.push(<div key='eng'>
|
||||
{`${translate('ENG')}: `}
|
||||
<span className={diffClass(m.enginecapacity, mm.enginecapacity)}>{m.enginecapacity}{units.MJ}</span>
|
||||
<span className={diffClass(m.engcap, mm.getEnginesCapacity())}>{m.engcap}{units.MJ}</span>
|
||||
{' / '}
|
||||
<span className={diffClass(m.enginerecharge, mm.enginerecharge)}>{m.enginerecharge}{units.MW}</span>
|
||||
<span className={diffClass(m.engrate, mm.getEnginesRechargeRate())}>{m.engrate}{units.MW}</span>
|
||||
</div>);
|
||||
}
|
||||
|
||||
// Common items
|
||||
|
||||
let mCost = m.cost || 0;
|
||||
let mmCost = mm ? mm.cost : 0;
|
||||
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
|
||||
|
||||
let mMass = m.mass || 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>);
|
||||
|
||||
let massDiff = mMass - mmMass;
|
||||
let mCap = m.fuel || m.cargo || 0;
|
||||
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
|
||||
let capDiff = mCap - mmCap;
|
||||
if (m.grp == 'fsd' || massDiff || capDiff) {
|
||||
let fsd = m.grp == 'fsd' ? m : null;
|
||||
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
|
||||
@@ -272,5 +243,11 @@ export function diffDetails(language, m, mm) {
|
||||
}
|
||||
}
|
||||
|
||||
return <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div>;
|
||||
let mIntegrity = m.integrity || 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>);
|
||||
}
|
||||
|
||||
return propDiffs.length > 0 ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* Generates a URL for the outiffing page
|
||||
* @param {String} shipId Ship Id
|
||||
@@ -7,15 +6,18 @@
|
||||
* @return {String} URL
|
||||
*/
|
||||
export function outfitURL(shipId, code, buildName) {
|
||||
let parts = ['/outfit/', shipId];
|
||||
let path = '/outfit/' + shipId;
|
||||
|
||||
let sepChar = '?';
|
||||
|
||||
if (code) {
|
||||
parts.push('/', code);
|
||||
path = path + sepChar + 'code=' + encodeURIComponent(code);
|
||||
sepChar = '&';
|
||||
}
|
||||
|
||||
if (buildName) {
|
||||
parts.push('?bn=', encodeURIComponent(buildName));
|
||||
path = path + sepChar + 'bn=' + encodeURIComponent(buildName);
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -58,3 +58,28 @@ export function shallowEqual(objA, objB) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a URL-safe base-64 encoded string in to a normal version.
|
||||
* Coriolis used to use a different encoding system, and some old
|
||||
* data might be bookmarked or on local storage, so we keep this
|
||||
* around and use it when decoding data from the old-style URLs to
|
||||
* be safe.
|
||||
* @param {string} data the string
|
||||
* @return {string} the converted string
|
||||
*/
|
||||
export function fromUrlSafe(data) {
|
||||
return data ? data.replace(/-/g, '/').replace(/_/g, '+') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object is empty
|
||||
* @param {object} obj the object
|
||||
* @return {bool} true if the object is empty, otherwise false
|
||||
*/
|
||||
export function isEmpty(obj) {
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"name": "Coriolis EDCD Edition",
|
||||
"short_name": "Coriolis",
|
||||
"icons": [
|
||||
{
|
||||
@@ -27,7 +27,7 @@
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "http:\/\/coriolis.io",
|
||||
"start_url": "https:\/\/edcd.coriolis.io",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
|
||||
38
src/index.html → src/index.ejs
Executable file → Normal file
38
src/index.html → src/index.ejs
Executable file → Normal file
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
|
||||
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
|
||||
<head>
|
||||
<title>Coriolis</title>
|
||||
<title>Coriolis EDCD Edition</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
|
||||
<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="mobile-web-app-capable" content="yes">
|
||||
@@ -22,24 +22,24 @@
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
<meta name="theme-color" content="#000000">
|
||||
|
||||
<script>
|
||||
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
|
||||
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
|
||||
window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>';
|
||||
</script>
|
||||
<% if (htmlWebpackPlugin.options.uaTracking) { %>
|
||||
<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');
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body style="background-color:#000;">
|
||||
<section id="coriolis"></section>
|
||||
<script>
|
||||
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
|
||||
window.CORIOLIS_VERSION = '{%= o.htmlWebpackPlugin.options.version %}';
|
||||
window.CORIOLIS_DATE = '{%= new Date().toISOString().slice(0, 10) %}';
|
||||
</script>
|
||||
<script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script>
|
||||
<script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
{% if (o.htmlWebpackPlugin.options.uaTracking) { %}
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto');
|
||||
{% } %}
|
||||
</script>
|
||||
<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>
|
||||
@@ -18,6 +18,12 @@
|
||||
@import 'error';
|
||||
@import 'sortable';
|
||||
@import 'loader';
|
||||
@import 'pips';
|
||||
@import 'boost';
|
||||
@import 'movement';
|
||||
@import 'shippicker';
|
||||
@import 'defence';
|
||||
@import 'offence';
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
@@ -43,6 +49,7 @@ div, a, li {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page {
|
||||
@@ -163,4 +170,4 @@ footer {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/less/boost.less
Executable file
14
src/less/boost.less
Executable file
@@ -0,0 +1,14 @@
|
||||
#boost {
|
||||
button {
|
||||
font-size: 1.2em;
|
||||
background: @primary-bg;
|
||||
color: @primary;
|
||||
border: 1px solid @primary;
|
||||
&.selected {
|
||||
// Shown when button is selected
|
||||
background: @primary;
|
||||
color: @primary-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ svg {
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 0.8em;
|
||||
font-size: 1.2em;
|
||||
font-family: @fStandard;
|
||||
fill: @primary-disabled;
|
||||
}
|
||||
|
||||
@@ -43,6 +44,9 @@ svg {
|
||||
|
||||
.label, .text-tip {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.x {
|
||||
fill: @fg;
|
||||
}
|
||||
|
||||
@@ -55,7 +59,7 @@ svg {
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.75em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.text-tip {
|
||||
|
||||
14
src/less/defence.less
Executable file
14
src/less/defence.less
Executable file
@@ -0,0 +1,14 @@
|
||||
#defence {
|
||||
table {
|
||||
background-color: @bgBlack;
|
||||
color: @primary;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.icon {
|
||||
stroke: @primary;
|
||||
stroke-width: 20;
|
||||
fill: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ header {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
box-sizing: border-box;
|
||||
.user-select-none();
|
||||
|
||||
.menu {
|
||||
@@ -186,6 +187,7 @@ header {
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -200,4 +202,4 @@ header {
|
||||
margin:0px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
// Standard icons
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@@ -25,3 +26,41 @@
|
||||
height: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiction icons - hard-code stroke/fill
|
||||
.modicon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 1.1em;
|
||||
height: 1em;
|
||||
stoke: @fg;
|
||||
stroke-width: 20;
|
||||
fill: transparent;
|
||||
|
||||
|
||||
&.sm {
|
||||
width: 0.8em;
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
&.tn {
|
||||
width: 0.6em;
|
||||
height: 0.5em;
|
||||
}
|
||||
|
||||
&.lg {
|
||||
width: 1.6em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
&.xl {
|
||||
width: 2.1em;
|
||||
height: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
stroke: @fg;
|
||||
stroke-width: 10;
|
||||
fill: @fg;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user