Compare commits
519 Commits
v2.10.0
...
v2.21.1dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eaeea325d | ||
|
|
9695526352 | ||
|
|
4f35bc38db | ||
|
|
d9c7c1a0fd | ||
|
|
a94e66e5ed | ||
|
|
e9c41c6439 | ||
|
|
b24ceb95f5 | ||
|
|
6ba6e0f69b | ||
|
|
9aca49e539 | ||
|
|
01f1cdb175 | ||
|
|
c1356906bb | ||
|
|
8229537e5f | ||
|
|
6cfa2161ec | ||
|
|
fa3cf46786 | ||
|
|
ac40dfd018 | ||
|
|
4c32d559bb | ||
|
|
02ee12586f | ||
|
|
496f15a83e | ||
|
|
acf6af8f95 | ||
|
|
225b00b9ff | ||
|
|
7f2b7415ea | ||
|
|
d1d9ae4dac | ||
|
|
ac7ccfb6a3 | ||
|
|
05c2c41762 | ||
|
|
10f5133282 | ||
|
|
42658a8167 | ||
|
|
6dbf38dbb4 | ||
|
|
64306e2366 | ||
|
|
f259df85cd | ||
|
|
1090589cb2 | ||
|
|
29e09fcdda | ||
|
|
36fd4aeaff | ||
|
|
c46a59e3b1 | ||
|
|
8a3a21972a | ||
|
|
6839669e04 | ||
|
|
5645e20505 | ||
|
|
15fe8f6261 | ||
|
|
855fafa94d | ||
|
|
4e10335ae7 | ||
|
|
21ea9ce579 | ||
|
|
ea07bbf4f9 | ||
|
|
8eed6fbe21 | ||
|
|
0859f2fbe9 | ||
|
|
71ba33edeb | ||
|
|
ce80d92b35 | ||
|
|
f17cf9b736 | ||
|
|
98579c774b | ||
|
|
509fa279e7 | ||
|
|
091ee87761 | ||
|
|
c0c20cc92e | ||
|
|
1341f7bca1 | ||
|
|
fe93db1d4b | ||
|
|
5db97ea773 | ||
|
|
1758e4f320 | ||
|
|
1a897c0419 | ||
|
|
32db3e3179 | ||
|
|
d830a8957a | ||
|
|
652ea48223 | ||
|
|
8c25b2b8f5 | ||
|
|
db4c56be8e | ||
|
|
f3bcffe2f9 | ||
|
|
bc5786d099 | ||
|
|
5959fe5daf | ||
|
|
649d338bb1 | ||
|
|
dcb058a718 | ||
|
|
1772bb5e7f | ||
|
|
30bd0adb06 | ||
|
|
44dfcf771c | ||
|
|
a1f8a7a930 | ||
|
|
b22887dfad | ||
|
|
28137fa3f4 | ||
|
|
9cbdc6055d | ||
|
|
fc93c61fcf | ||
|
|
3fa2e7ebd1 | ||
|
|
818628da0c | ||
|
|
adf90a8263 | ||
|
|
362923ac64 | ||
|
|
7d73838ce1 | ||
|
|
b3278ca9ec | ||
|
|
5707914ad5 | ||
|
|
9b697b24d8 | ||
|
|
098b088da6 | ||
|
|
14d62f31e1 | ||
|
|
e7b1f55d08 | ||
|
|
8f501896a1 | ||
|
|
befcb9b874 | ||
|
|
c70afa9a4c | ||
|
|
029c7dd4c2 | ||
|
|
1f83dba1ac | ||
|
|
6eae3405fd | ||
|
|
c0e1d9e4de | ||
|
|
7c0bd7aa88 | ||
|
|
394583584c | ||
|
|
4112e2aa6b | ||
|
|
aa19e0da72 | ||
|
|
bba9be1598 | ||
|
|
17998916b4 | ||
|
|
543089bcd9 | ||
|
|
7f35c78a65 | ||
|
|
b25798dd83 | ||
|
|
744b9ff78a | ||
|
|
8dc1457ebb | ||
|
|
982ad54fab | ||
|
|
912192cd7d | ||
|
|
74cd6d48da | ||
|
|
3467a7fe3f | ||
|
|
eb269a05ed | ||
|
|
c63cf4b3b0 | ||
|
|
11ed94454d | ||
|
|
8df07645da | ||
|
|
5666fdd250 | ||
|
|
0b55ae40fc | ||
|
|
3726e84697 | ||
|
|
a0c4341102 | ||
|
|
cd6b1038e8 | ||
|
|
10583fd506 | ||
|
|
713694be56 | ||
|
|
f50293cf77 | ||
|
|
7c88fa477f | ||
|
|
f8df540fad | ||
|
|
61a01805cc | ||
|
|
a93915cf04 | ||
|
|
0f74c97fbf | ||
|
|
29713b69dc | ||
|
|
cc3c2cb9c8 | ||
|
|
b36a3959da | ||
|
|
3b26bfc0e9 | ||
|
|
c59b621963 | ||
|
|
977a8fa329 | ||
|
|
a5226fee83 | ||
|
|
e4069a3988 | ||
|
|
6527f9e11e | ||
|
|
9ddfcc894f | ||
|
|
f22a4f13e5 | ||
|
|
fc9337df67 | ||
|
|
98a8332cfa | ||
|
|
a78eedad48 | ||
|
|
85e1a1bd06 | ||
|
|
25b2c04309 | ||
|
|
54a84d0e42 | ||
|
|
af0a4d1def | ||
|
|
97cdde84ce | ||
|
|
a65129b277 | ||
|
|
3b40a49918 | ||
|
|
d95345b476 | ||
|
|
2ba2d95017 | ||
|
|
4d0ffedcc8 | ||
|
|
84abde4fc5 | ||
|
|
8897f1e4b1 | ||
|
|
6f33cacb7a | ||
|
|
b1a8c0ad09 | ||
|
|
13ed635803 | ||
|
|
5a44909ebf | ||
|
|
6cbb80693d | ||
|
|
0b90d254f9 | ||
|
|
641d36205c | ||
|
|
6e38b6ea4d | ||
|
|
84b4d8fabb | ||
|
|
c0e1f7e746 | ||
|
|
d734c12168 | ||
|
|
340d94eb3e | ||
|
|
9b0b31648c | ||
|
|
8346358c37 | ||
|
|
b935700a12 | ||
|
|
2f4a5a4830 | ||
|
|
e951ce8e9d | ||
|
|
fc6d42f483 | ||
|
|
7df1431ad2 | ||
|
|
4d4680961e | ||
|
|
1cae99b812 | ||
|
|
3727d19311 | ||
|
|
c45c093f4f | ||
|
|
b613d3e312 | ||
|
|
4cff9247b0 | ||
|
|
b9a26ec28d | ||
|
|
245f81e888 | ||
|
|
204c1ba9f2 | ||
|
|
a7a4be133c | ||
|
|
14f9a46d7a | ||
|
|
003dd040dc | ||
|
|
97cf53217e | ||
|
|
7c1805826d | ||
|
|
d6a2b4dfc4 | ||
|
|
d70952520a | ||
|
|
a57867b806 | ||
|
|
17d7583cfc | ||
|
|
55ad95081b | ||
|
|
da8da1759f | ||
|
|
b4e758b9ee | ||
|
|
3ba41db699 | ||
|
|
cfc47cf483 | ||
|
|
38c8be995b | ||
|
|
492207700b | ||
|
|
b69adefbf8 | ||
|
|
361299f51e | ||
|
|
025091c3f6 | ||
|
|
a54d70036b | ||
|
|
9e67b5962c | ||
|
|
ed0c080163 | ||
|
|
21389d84fc | ||
|
|
0fe54631ad | ||
|
|
36ea5200f2 | ||
|
|
d9934160a6 | ||
|
|
b9c92c48d3 | ||
|
|
6d23df6156 | ||
|
|
a913ab72c2 | ||
|
|
f6d33a2ac1 | ||
|
|
5a3fec33a7 | ||
|
|
95734aca2c | ||
|
|
377254be9d | ||
|
|
ad9905a5e0 | ||
|
|
6fc5a9699e | ||
|
|
0ee86f4836 | ||
|
|
85593367f7 | ||
|
|
465ea61b06 | ||
|
|
2d5507e951 | ||
|
|
84e20be153 | ||
|
|
60080c02b0 | ||
|
|
2a0d4179d0 | ||
|
|
6dd55e0cd4 | ||
|
|
0adc6e0c6f | ||
|
|
e155356701 | ||
|
|
ef44b50980 | ||
|
|
6785d16c87 | ||
|
|
76c670b7d5 | ||
|
|
717303a72f | ||
|
|
e13125b174 | ||
|
|
8aa7964b81 | ||
|
|
5e5f23d296 | ||
|
|
05837f99ff | ||
|
|
70f620c2c3 | ||
|
|
10ffb987ea | ||
|
|
13ed785551 | ||
|
|
69d2e38647 | ||
|
|
c45abbdbcf | ||
|
|
2655b001a9 | ||
|
|
3c7f4edb1b | ||
|
|
663cbab401 | ||
|
|
897ca9ca43 | ||
|
|
b31a071859 | ||
|
|
8c2788fd78 | ||
|
|
dee8fa400c | ||
|
|
2339327473 | ||
|
|
f6f9239dd5 | ||
|
|
cd4c8c8c10 | ||
|
|
c25eda8b64 | ||
|
|
b6edf0e034 | ||
|
|
af4277fc7e | ||
|
|
acd774abe5 | ||
|
|
9081353634 | ||
|
|
9c7fa37a72 | ||
|
|
d92e11893a | ||
|
|
f55ab00bf5 | ||
|
|
36265aa2a3 | ||
|
|
bfedcdbffd | ||
|
|
a5d10c4a76 | ||
|
|
2962ce1945 | ||
|
|
0063d2955e | ||
|
|
9787a18666 | ||
|
|
d8cd3197b5 | ||
|
|
e07c4f65ab | ||
|
|
c3108f773a | ||
|
|
a400821268 | ||
|
|
ca4bac07da | ||
|
|
0038eacc3f | ||
|
|
4bd633a0d4 | ||
|
|
ee837f0b04 | ||
|
|
016e18cc89 | ||
|
|
1d6ac53183 | ||
|
|
1e32dfa463 | ||
|
|
4431753570 | ||
|
|
6fdb57318c | ||
|
|
649db019de | ||
|
|
1d528be0ef | ||
|
|
2d6f6df83d | ||
|
|
7fa998f276 | ||
|
|
6a3157a4c8 | ||
|
|
74efa50576 | ||
|
|
e99be78c54 | ||
|
|
e386232de1 | ||
|
|
a15896044d | ||
|
|
72c74513ce | ||
|
|
34d6d13cb2 | ||
|
|
323bb8e064 | ||
|
|
b628f8ef9b | ||
|
|
1900216d36 | ||
|
|
c045ed81c1 | ||
|
|
39edec60e3 | ||
|
|
259214e907 | ||
|
|
2e1c53392d | ||
|
|
7a77d12686 | ||
|
|
763a7714ed | ||
|
|
e89f35d654 | ||
|
|
3196751b7a | ||
|
|
f221024974 | ||
|
|
66cab8a0d4 | ||
|
|
daa49a8cd4 | ||
|
|
b8601ff240 | ||
|
|
5df3eeca64 | ||
|
|
274e4ac2ca | ||
|
|
f6485251ca | ||
|
|
c03fb70def | ||
|
|
8652a2891b | ||
|
|
a09af0a9eb | ||
|
|
c7d4b93fba | ||
|
|
745c0db530 | ||
|
|
aa56ab8d6c | ||
|
|
043c430221 | ||
|
|
dc5cc5855e | ||
|
|
59d6266e2b | ||
|
|
7495ba67f8 | ||
|
|
e649683a4d | ||
|
|
bec58a5772 | ||
|
|
e8f9ae8a9c | ||
|
|
679382e220 | ||
|
|
7f86782f54 | ||
|
|
f80b7d972f | ||
|
|
c0bd489c1b | ||
|
|
41e4c2107d | ||
|
|
cfc95c272a | ||
|
|
f778f9ceae | ||
|
|
7fb6170bcb | ||
|
|
fa37428cd3 | ||
|
|
2a2d9d3456 | ||
|
|
a91efb681f | ||
|
|
386e05be8f | ||
|
|
65e7bf609d | ||
|
|
7a58d97652 | ||
|
|
c5118da417 | ||
|
|
13b505525d | ||
|
|
b682dec363 | ||
|
|
6aa98e2214 | ||
|
|
8fba988222 | ||
|
|
24a82efe50 | ||
|
|
8054fa9267 | ||
|
|
a0e39a3725 | ||
|
|
185d6d0c51 | ||
|
|
1975e96848 | ||
|
|
ab37d228ea | ||
|
|
f48483d754 | ||
|
|
e6cfd33435 | ||
|
|
c29126ce1d | ||
|
|
c52170b731 | ||
|
|
6607dd31bf | ||
|
|
c6c74be38d | ||
|
|
41c6062ff9 | ||
|
|
bbb96a73b7 | ||
|
|
9eb3b9e017 | ||
|
|
6b3e94729c | ||
|
|
fb48f2b5d4 | ||
|
|
cfffc77777 | ||
|
|
f7089f358d | ||
|
|
06c4f2ce46 | ||
|
|
83eb0abd92 | ||
|
|
4199b33c47 | ||
|
|
23cd4bff5a | ||
|
|
b65f95fe77 | ||
|
|
32160c94e1 | ||
|
|
ac02fba98b | ||
|
|
cde0108cba | ||
|
|
39dc7e4a46 | ||
|
|
9943f784a8 | ||
|
|
88ce45f29e | ||
|
|
7157e876ca | ||
|
|
0cf88cf7ca | ||
|
|
10dfdc3627 | ||
|
|
76bdefcda6 | ||
|
|
1c2c8cc5f9 | ||
|
|
58f853de5b | ||
|
|
c052297bf7 | ||
|
|
9e78cd1076 | ||
|
|
79f4deacea | ||
|
|
ff42c4c711 | ||
|
|
02d31d49d8 | ||
|
|
64f47fcc24 | ||
|
|
0ceb8acd64 | ||
|
|
78579e2e13 | ||
|
|
cf4e1d3935 | ||
|
|
d1be0bb680 | ||
|
|
e70ebad3f7 | ||
|
|
4dfe5c3abf | ||
|
|
384d9f4614 | ||
|
|
47434c68f9 | ||
|
|
af88afb6b5 | ||
|
|
536eb1efa5 | ||
|
|
c4c763089e | ||
|
|
cdfd4c0d8e | ||
|
|
f9bb8836e5 | ||
|
|
58b2634c8c | ||
|
|
093ae008ce | ||
|
|
5f62fc0cdc | ||
|
|
e7a4b4ac26 | ||
|
|
66e9944cb5 | ||
|
|
ad8528c248 | ||
|
|
07d22cd8e4 | ||
|
|
a6d5922d77 | ||
|
|
958d7bff99 | ||
|
|
7819b80be4 | ||
|
|
2ca50a4658 | ||
|
|
09ff4fd128 | ||
|
|
3e53863f9e | ||
|
|
63d2289f97 | ||
|
|
2663ef2e66 | ||
|
|
d4bdf47d62 | ||
|
|
660ee7c4bf | ||
|
|
1db1f3070b | ||
|
|
3dba82c497 | ||
|
|
25e7b7a9f7 | ||
|
|
9582212ae0 | ||
|
|
7d2b60c327 | ||
|
|
0121a0064e | ||
|
|
2aa96fc819 | ||
|
|
8d81db0a3a | ||
|
|
e5ba35fde9 | ||
|
|
885cd32cb0 | ||
|
|
18d8ed6558 | ||
|
|
9618ece4b4 | ||
|
|
a80a77a422 | ||
|
|
3806be3ddd | ||
|
|
3e803fef30 | ||
|
|
12956d435a | ||
|
|
a3381007f3 | ||
|
|
1efe4ee5e5 | ||
|
|
ec21f93d3c | ||
|
|
f384b32ed6 | ||
|
|
22d8f34c75 | ||
|
|
6128cd8322 | ||
|
|
386f403430 | ||
|
|
5f7d9aea89 | ||
|
|
b367c449a9 | ||
|
|
26b3dff9d4 | ||
|
|
873a62e3f0 | ||
|
|
d967ab375e | ||
|
|
fcf2d6a72c | ||
|
|
843ced15bf | ||
|
|
813db9340f | ||
|
|
acbd8a3298 | ||
|
|
561e22e894 | ||
|
|
05ac0a528a | ||
|
|
c040353f6e | ||
|
|
f23a8fa0c8 | ||
|
|
ba93467646 | ||
|
|
00d480860f | ||
|
|
c94384acb8 | ||
|
|
0c2c0ac6ef | ||
|
|
61a33a331e | ||
|
|
e374a6f2c6 | ||
|
|
dbd84dce28 | ||
|
|
9d554f9c68 | ||
|
|
576cf56735 | ||
|
|
e2aaabbc16 | ||
|
|
ef226898c0 | ||
|
|
a0db235e5a | ||
|
|
5bf05ba775 | ||
|
|
c073b1fa2a | ||
|
|
5f58307bf3 | ||
|
|
8741b17a5e | ||
|
|
4c1fa09795 | ||
|
|
ce7df2d01f | ||
|
|
b433b0ea7c | ||
|
|
20868d6b44 | ||
|
|
33103dbee9 | ||
|
|
2a05ac5a85 | ||
|
|
a013828128 | ||
|
|
e19510b3d4 | ||
|
|
390f2048f2 | ||
|
|
0bb732300e | ||
|
|
fd017df561 | ||
|
|
0ed16b9a6f | ||
|
|
865978fcc1 | ||
|
|
a43f9930de | ||
|
|
c13cd23d54 | ||
|
|
ed1f52a114 | ||
|
|
7dd063f04e | ||
|
|
6e9fc1d1d9 | ||
|
|
cae0172e48 | ||
|
|
e2b492ee8d | ||
|
|
545ddc7492 | ||
|
|
d0b7c58a1d | ||
|
|
a9ad094422 | ||
|
|
68154333c2 | ||
|
|
5df2db5879 | ||
|
|
5a34db0d2f | ||
|
|
6f50be1e7e | ||
|
|
d15fefcf1b | ||
|
|
07bf1b400c | ||
|
|
9f975a958e | ||
|
|
c2a240bab0 | ||
|
|
40c3bf723f | ||
|
|
7a92ace2db | ||
|
|
500f5b8310 | ||
|
|
44830a4de6 | ||
|
|
f3f13e7ba8 | ||
|
|
0269a64ae1 | ||
|
|
5d6cdcbd23 | ||
|
|
81906a7bd2 | ||
|
|
b25b038934 | ||
|
|
b469fa520e | ||
|
|
4f865896c7 | ||
|
|
3b50dddef2 | ||
|
|
380e9c2e87 | ||
|
|
1c1443c862 | ||
|
|
0529baac4a | ||
|
|
7dab220009 | ||
|
|
0ea0f8cdf2 | ||
|
|
eebd59413b | ||
|
|
f4a635eb43 | ||
|
|
0e57258cc5 | ||
|
|
67462c3278 | ||
|
|
fce8129fa2 | ||
|
|
1d76f3ec31 | ||
|
|
707dbeecf8 | ||
|
|
56672f5830 | ||
|
|
13a0bf9d42 | ||
|
|
1bff10c973 | ||
|
|
1d4aece7cc | ||
|
|
cdb79f499a | ||
|
|
837dbb3677 | ||
|
|
b2c718d614 |
123
.appveyor.yml
123
.appveyor.yml
@@ -1,124 +1,78 @@
|
|||||||
|
image: Visual Studio 2019
|
||||||
|
clone_depth: 400
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
global:
|
|
||||||
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
|
|
||||||
# /E:ON and /V:ON options are not enabled in the batch script intepreter
|
|
||||||
# See: http://stackoverflow.com/a/13751649/163740
|
|
||||||
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
|
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
- PYTHON: "C:\\Python37-x64"
|
||||||
- PYTHON: "C:\\Python36"
|
# Should be enabled only for build process debugging
|
||||||
PYTHON_VERSION: "3.6.x"
|
# init:
|
||||||
PYTHON_ARCH: "32"
|
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||||
init:
|
|
||||||
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
install:
|
install:
|
||||||
# If there is a newer build queued for the same PR, cancel this one.
|
- ps: echo("OS version:")
|
||||||
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
- ps: "[System.Environment]::OSVersion.Version"
|
||||||
# purpose but it is problematic because it tends to cancel builds pushed
|
|
||||||
# directly to master instead of just PR builds (or the converse).
|
|
||||||
# credits: JuliaLang developers.
|
|
||||||
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
|
||||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
|
||||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
|
||||||
throw "There are newer queued builds for this pull request, failing early." }
|
|
||||||
|
|
||||||
- ECHO "Filesystem root:"
|
- ps: echo("Filesystem - root:")
|
||||||
- ps: "ls \"C:/\""
|
- ps: "ls \"C:\\\""
|
||||||
|
|
||||||
- ECHO "Filesystem projects root:"
|
- ps: echo("Filesystem - projects root:")
|
||||||
- ps: "ls \"C:\\projects\\\""
|
- ps: "ls \"C:\\projects\\\""
|
||||||
|
|
||||||
- ECHO "Filesystem pyfa root:"
|
- ps: echo("Filesystem - pyfa root:")
|
||||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\""
|
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\\""
|
||||||
|
|
||||||
- ECHO "Installed SDKs:"
|
- ps: echo("Filesystem - installed SDKs:")
|
||||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
- ps: "ls \"C:\\Program Files (x86)\\Windows Kits\\\""
|
||||||
|
|
||||||
# Prepend newly installed Python to the PATH of this build (this cannot be
|
# Prepend newly installed Python to the PATH of this build (this cannot be
|
||||||
# done from inside the powershell script as it would require to restart
|
# done from inside the powershell script as it would require to restart
|
||||||
# the parent CMD process).
|
# the parent CMD process).
|
||||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
- cmd: "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||||
|
|
||||||
- "python --version"
|
- cmd: "python --version"
|
||||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
- cmd: "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||||
|
|
||||||
# Upgrade to the latest version of pip to avoid it displaying warnings
|
# Upgrade to the latest version of pip to avoid it displaying warnings
|
||||||
# about it being out of date.
|
# about it being out of date.
|
||||||
- "pip install --disable-pip-version-check --user --upgrade pip"
|
- cmd: "python -m pip install --upgrade pip"
|
||||||
|
|
||||||
# Install the build dependencies of the project. If some dependencies contain
|
# Install the build dependencies of the project. If some dependencies contain
|
||||||
# compiled extensions and are not provided as pre-built wheel packages,
|
# compiled extensions and are not provided as pre-built wheel packages,
|
||||||
# pip will build them from source using the MSVC compiler matching the
|
# pip will build them from source using the MSVC compiler matching the
|
||||||
# target Python version and architecture
|
# target Python version and architecture
|
||||||
- ECHO "Install pip requirements:"
|
- ps: echo("Install pip requirements:")
|
||||||
- "pip install -r requirements.txt"
|
# This one is needed to build wxpython 4.0.6 on windows
|
||||||
- "pip install PyInstaller"
|
- cmd: "python -m pip install pathlib2"
|
||||||
|
- cmd: "python -m pip install -r requirements.txt"
|
||||||
|
- cmd: "python -m pip install PyInstaller==3.6"
|
||||||
|
|
||||||
before_build:
|
before_build:
|
||||||
# directory that will contain the built files
|
# directory that will contain the built files
|
||||||
- ps: $env:PYFA_DIST_DIR = "c:\projects\$env:APPVEYOR_PROJECT_SLUG\dist"
|
- ps: $env:PYFA_DIST_DIR = "c:\projects\$env:APPVEYOR_PROJECT_SLUG\dist"
|
||||||
- ps: $env:PYFA_VERSION = (python ./scripts/dump_version.py)
|
- ps: $env:PYFA_VERSION = (python ./scripts/dump_version.py)
|
||||||
- ps: echo("pyfa version ")
|
- ps: echo("pyfa version $env:PYFA_VERSION")
|
||||||
- ps: echo ($env:PYFA_VERSION)
|
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- ECHO "Build pyfa:"
|
- ps: echo("Build pyfa:")
|
||||||
|
# Build gamedata DB
|
||||||
##########
|
- cmd: "python db_update.py"
|
||||||
# PyInstaller - create binaries for pyfa
|
|
||||||
##########
|
|
||||||
# Build command for PyInstaller
|
# Build command for PyInstaller
|
||||||
- "python -m PyInstaller --noupx --clean --windowed --noconsole -y pyfa.spec"
|
- cmd: "python -m PyInstaller --noupx --clean --windowed --noconsole -y pyfa.spec"
|
||||||
# Copy over manifest (See pyfa-org/pyfa#1622)
|
# Copy over manifest (See pyfa-org/pyfa#1622)
|
||||||
- ps: xcopy /y dist_assets\win\pyfa.exe.manifest $env:PYFA_DIST_DIR\pyfa\
|
- ps: xcopy /y dist_assets\win\pyfa.exe.manifest $env:PYFA_DIST_DIR\pyfa\
|
||||||
# Not really sure if this is needed, but why not
|
# InnoScript EXE building. This is in a separate script because I don't feel like copying over the logic to AppVeyor script right now...
|
||||||
- ps: xcopy /y dist_assets\win\Microsoft.VC90.CRT.manifest $env:PYFA_DIST_DIR\pyfa\
|
- cmd: "python dist_assets/win/dist.py"
|
||||||
|
|
||||||
##########
|
|
||||||
# InnoScript EXE building
|
|
||||||
# This is in a separate script because I don't feel like copying over the logic to AppVeyor script right now...
|
|
||||||
##########
|
|
||||||
- "python dist_assets/win/dist.py"
|
|
||||||
- ps: dir $env:PYFA_DIST_DIR/
|
- ps: dir $env:PYFA_DIST_DIR/
|
||||||
#- ECHO "Build pyfa (Debug):"
|
|
||||||
#- copy C:\projects\pyfa\dist_assets\win\pyfa_debug.spec C:\projects\pyfa\pyfa_debug.spec
|
|
||||||
#- "pyinstaller.exe --clean --noconfirm --windowed --upx-dir=C:\\projects\\pyfa\\scripts\\upx.exe C:\\projects\\pyfa\\pyfa_debug.spec"
|
|
||||||
|
|
||||||
build: on
|
|
||||||
|
|
||||||
after_build:
|
after_build:
|
||||||
- ps: "ls \"./\""
|
- ps: "ls \"./\""
|
||||||
#- ps: "ls \"C:\\projects\\pyfa\\build\\pyfa\\\""
|
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*"
|
||||||
# - ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\build\\exe.win32-2.7\\\""
|
|
||||||
# Zip
|
|
||||||
# APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER
|
|
||||||
#- 7z a build.zip -r C:\projects\pyfa\build\pyfa\*.*
|
|
||||||
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*.*"
|
|
||||||
#- 7z a pyfa_debug.zip -r C:\projects\pyfa\dist\pyfa_debug\*.*
|
|
||||||
|
|
||||||
on_success:
|
|
||||||
# Do nothing right now
|
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
#- tox
|
# Ha... we're just building
|
||||||
#- "py.test --cov=./"
|
|
||||||
# Run the project tests
|
|
||||||
# - "%CMD_IN_ENV% python C:/projects/eve-gnosis/setup.py nosetests"
|
|
||||||
|
|
||||||
after_test:
|
|
||||||
# If tests are successful, create binary packages for the project.
|
|
||||||
# - "%CMD_IN_ENV% python setup.py bdist_wheel"
|
|
||||||
# - "%CMD_IN_ENV% python setup.py bdist_wininst"
|
|
||||||
# - "%CMD_IN_ENV% python setup.py bdist_msi"
|
|
||||||
# - ps: "ls dist"
|
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
# Archive the generated packages in the ci.appveyor.com build report.
|
|
||||||
- path: pyfa*-win.zip
|
- path: pyfa*-win.zip
|
||||||
- path: pyfa*-win.exe
|
- path: pyfa*-win.exe
|
||||||
#- path: pyfa_debug.zip
|
|
||||||
# name: Pyfa_debug
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
tag: $(pyfa_version)
|
tag: $(pyfa_version)
|
||||||
@@ -126,10 +80,9 @@ deploy:
|
|||||||
description: 'Release description'
|
description: 'Release description'
|
||||||
provider: GitHub
|
provider: GitHub
|
||||||
auth_token:
|
auth_token:
|
||||||
secure: BfNHO66ff5hVx2O2ORbl49X0U/5h2V2T0IuRZDwm7fd1HvsVluF0wRCbl29oRp1M
|
secure: X+U3hOAMTt7HGXCR/LXaGNF6qyhUXetrjz5+xlWiNJQ3XEdzhZZmHK75m0Hm6qre
|
||||||
draft: true
|
draft: true
|
||||||
|
force_update: false
|
||||||
|
# deploy on tag push only
|
||||||
on:
|
on:
|
||||||
APPVEYOR_REPO_TAG: true # deploy on tag push only
|
APPVEYOR_REPO_TAG: true
|
||||||
#on_success:
|
|
||||||
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
|
||||||
#
|
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ language: python
|
|||||||
git:
|
git:
|
||||||
depth: 400
|
depth: 400
|
||||||
python:
|
python:
|
||||||
- 3.6
|
- 3.8
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode7.3
|
osx_image: xcode11.3
|
||||||
language: generic
|
language: generic
|
||||||
env: PYTHON=3.6.1
|
|
||||||
before_install:
|
before_install:
|
||||||
- bash scripts/setup-osx.sh
|
- bash scripts/setup-osx.sh
|
||||||
install:
|
install:
|
||||||
|
- python3 db_update.py
|
||||||
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
|
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
|
||||||
- bash scripts/package-osx.sh
|
- bash scripts/package-osx.sh
|
||||||
before_deploy:
|
before_deploy:
|
||||||
@@ -21,7 +21,7 @@ before_deploy:
|
|||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
api_key:
|
api_key:
|
||||||
secure: Xfu0xApoB0zUPLXl29aYUulVC3iA4/3bXQwwADKCfAKZwxgNon4dLbO7Rie5/7Ukf2POL0KwmRaQGN3kOr+XSoIVTE4M5sXxnhiaaLGKQ+48hDizLE6JuXcZGJvkxUaghaTzIdCwHsG7VGBsPfQgfGsjJcfBp8tFNLmRyM/Jpsr8T6BR2MxtBIEUVy8zrOWFNZqnmWrY2pWMsB9fYt3JFNdpqeIgRAYqbBsBcZQ1MngLTi3ztuYS5IaF+lk06RrnBlHmUsJu/5nCvIpvPvD0i2BLZ3Uu0+Fn+8QWUgjJEL9MNseXZMXynu05xd8YRk7Ajc9CUrzQIIbAktyteYp85kE3pUJHmrMLcXhh7nqkwttR5/47Zwa3OLJLJFKBxMx6wY5jFkJjkV08850B7aWrmTFl/Eqc3Q5nZMuiEt3wFRbjxHi9h1mTN/fkxfRRHg8u3ENGPR+ZPiFC3J18qtks/B/hsKjjHvZP1i79OYlET4V/zyLyyQkCbpDaARQANuotLYJyZ7tH+KWEyRsvTi0M9Yev9mNNw6aI4vzh4HfkEhvcvnWnYwckPj1dnjQ573Qpw0Z9wsconoWfHAn+hBDt3+YLMrrFZl++mCRskHH1mZChX3aGMDi49zD0kfxBUkYPOAhguc6PwudBxHUZP+O6T/SoHylff6EizCE/k5dGeAk=
|
secure: D8tBW0kyHlKf/sXS69aIuexsYTx9auY2DzudKFlfcvdzqat4N2XZqZbZCTVd7YVvptQ8Dj0oZ/p3KUxEGpnJZmlTeJL142rpM/qaNd6wOIMy2yUde/aZl+W9JLFNQp7KHutM+MxObYLzJGihx/8YsupmFx6lxgdngGDXtXYZe/ruDIWDs92ShoKJ4vlce9Csm7eGKv7wv6Z6V9sD5FS3E9J8xdWStHxsbrkPBOflmG+uHU09dpEqzUm+ZYROIoTwig1Xbw3fw+gfjmNrfdSU4fAJcVZI1hrgoenZyJbMfhI2Ej/nZdbZgaXcZNF/eUpqOGgbPe1JljqFnHTbexcE+LPBVyAToScsGMpByHhig67DrZ0nk9gSZoC6CPNl5YS6xub+5dncMJ3P5L03DOGYRu4SL9NczbeuQyKuea7+JPP/8VLwfFDSEqbNEAmgzABAzrdfano+VXtuBuE/Tiy5eE7le9hJu6aSQoKW1SA3cUhMsmr2amzdO96sh+PN8FA1oNr45Yuy0pqOj4SUIkb8JUy4th7vgdhljEkSxrHDK1UcHpxUTp+IIUZkZVVk50aH68dQZxGwSTVOeRxpjrTcEf7VCGaM98qxi/ZK4RW6Ewiq0eo0AxwEeB2Zm841lycGPR/406vM9/ZBzv5IhELIdDdVWTk+dGjJBXB8z5hPJOg=
|
||||||
file_glob: true
|
file_glob: true
|
||||||
file: "dist/pyfa-*.zip"
|
file: "dist/pyfa-*.zip"
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
|
|||||||
102
CONTRIBUTING.md
Normal file
102
CONTRIBUTING.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Contribution
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.6
|
||||||
|
- Git CLI installed
|
||||||
|
- Python, pip and git are all available as command-line commands (add to the path if needed)
|
||||||
|
|
||||||
|
Virtual environment will be created in *PyfaEnv* folder. Project will be cloned and run from the *PyfaDEV* folder. Separate virtual environment will be created so required libraries won't clutter the main python installation.
|
||||||
|
|
||||||
|
> Commands and screens were created on Windows 10. Please, update all the paths according to your OS.
|
||||||
|
|
||||||
|
## Setting up the project manually
|
||||||
|
|
||||||
|
Clone the repository
|
||||||
|
```
|
||||||
|
git clone <repo> PyfaDEV
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the virtual environment
|
||||||
|
```
|
||||||
|
python -m venv PyfaEnv
|
||||||
|
```
|
||||||
|
|
||||||
|
Activate the virtual environment
|
||||||
|
|
||||||
|
```
|
||||||
|
For cmd.exe: PyfaEnv\scripts\activate.bat
|
||||||
|
For bash: source <venv>/Scripts/activate
|
||||||
|
```
|
||||||
|
> For other OS check [Python documentation](https://docs.python.org/3/library/venv.html)
|
||||||
|
|
||||||
|
Install requirements for the project from *requirements.txt*
|
||||||
|
```
|
||||||
|
pip install -r PyfaDEV\requirements.txt
|
||||||
|
```
|
||||||
|
> For some Linux distributions, you may need to install separate wxPython bindings, such as `python-matplotlib-wx`
|
||||||
|
|
||||||
|
Check that the libs from *requirements.txt* are installed
|
||||||
|
```
|
||||||
|
pip list
|
||||||
|
```
|
||||||
|
|
||||||
|
Test that the project is starting properly
|
||||||
|
```
|
||||||
|
python PyfaDEV\pyfa.py
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Setting up the project with PyCharm/IntelliJ
|
||||||
|
|
||||||
|
Install PyCharm / Other IntelliJ product with Python plugin
|
||||||
|
|
||||||
|
After launching - select *Check out from Version Control* -> *GIt*
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Login to GitHub, paste the repo URL and select the folder to which to clone the project into, press *Clone*.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After process is complete, open *File* -> *Settings* -> *Project* -> *Project Interpreter*.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Press on options and add new virtual environment.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Open project tree view and double-click on the *requirements.txt*. Press *Install requirements*. Install all requirements.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Create new *Run Configuration*. Set correct *Script path* and *Python interpreter*.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Check that the project is starting properly.
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
Switch to the proper virtual environment
|
||||||
|
```
|
||||||
|
For cmd.exe: PyfaEnv\scripts\activate.bat
|
||||||
|
For bash: source <venv>/Scripts/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Install pytest
|
||||||
|
```
|
||||||
|
pip install pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
Switch to pyfa directory.
|
||||||
|
|
||||||
|
Run tests (any will do)
|
||||||
|
```
|
||||||
|
python -m pytest
|
||||||
|
py.test
|
||||||
|
```
|
||||||
|
|
||||||
|
More information on tests can be found on appropriate [Wiki page](https://github.com/pyfa-org/Pyfa/wiki/Developers:-Writing-Tests-for-Pyfa).
|
||||||
34
README.md
34
README.md
@@ -2,20 +2,19 @@
|
|||||||
|
|
||||||
[](https://pyfainvite.azurewebsites.net/) [](https://travis-ci.org/pyfa-org/Pyfa)
|
[](https://pyfainvite.azurewebsites.net/) [](https://travis-ci.org/pyfa-org/Pyfa)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## What is it?
|
## What is it?
|
||||||
|
|
||||||
pyfa, short for **py**thon **f**itting **a**ssistant, allows you to create, experiment with, and save ship fittings without being in game. Open source and written in Python, it is available on any platform where Python 2.x and wxWidgets are available, including Windows, Mac OS X, and Linux.
|
Pyfa, short for **py**thon **f**itting **a**ssistant, allows you to create, experiment with, and save ship fittings without being in game. Open source and written in Python, it is available on any platform where Python 3 and wxWidgets are available, including Windows, Mac OS X, and Linux.
|
||||||
|
|
||||||
## Latest Version and Changelogs
|
## Latest Version and Changelogs
|
||||||
The latest version along with release notes can always be found on the project's [Releases](https://github.com/DarkFenX/Pyfa/releases) page. pyfa will notify you if you are running an outdated version.
|
The latest version along with release notes can always be found on the project's [releases](https://github.com/pyfa-org/Pyfa/releases) page. Pyfa will notify you if you are running an outdated version.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Windows and OS X users are supplied self-contained builds of pyfa on the [latest releases](https://github.com/pyfa-org/Pyfa/releases/latest) page. An `.exe` installer is also available for Windows builds. Linux users can run pyfa using their distribution's Python interpreter. There is no official self-contained package for Linux, however, there are a number of third-party packages available through distribution-specific repositories.
|
Windows and OS X users are supplied self-contained builds of pyfa on the [latest releases](https://github.com/pyfa-org/Pyfa/releases/latest) page. An `.exe` installer is also available for Windows builds. Linux users can run pyfa using their distribution's Python interpreter. There is no official self-contained package for Linux, however, there are a number of third-party packages available through distribution-specific repositories.
|
||||||
|
|
||||||
#### OS X
|
### OS X
|
||||||
|
|
||||||
Apart from the official release, there is also a [Homebrew](http://brew.sh) option for installing pyfa on OS X. Please note this is maintained by a third-party and is not tested by pyfa developers. Simply fire up in terminal:
|
Apart from the official release, there is also a [Homebrew](http://brew.sh) option for installing pyfa on OS X. Please note this is maintained by a third-party and is not tested by pyfa developers. Simply fire up in terminal:
|
||||||
```
|
```
|
||||||
$ brew install Caskroom/cask/pyfa
|
$ brew install Caskroom/cask/pyfa
|
||||||
@@ -27,34 +26,31 @@ The following is a list of pyfa packages available for certain distributions. Pl
|
|||||||
* Arch: https://aur.archlinux.org/packages/pyfa/
|
* Arch: https://aur.archlinux.org/packages/pyfa/
|
||||||
* Gentoo: https://github.com/ZeroPointEnergy/gentoo-pyfa-overlay
|
* Gentoo: https://github.com/ZeroPointEnergy/gentoo-pyfa-overlay
|
||||||
|
|
||||||
### Dependencies
|
## Contribution
|
||||||
If you wish to help with development or simply need to run pyfa through a Python interpreter, the following software is required:
|
If you wish to help with development or you need to run pyfa through a Python interpreter, check out [the instructions](https://github.com/pyfa-org/Pyfa/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
* Python 3.6
|
|
||||||
* Requirements as listed in `requirements.txt`
|
|
||||||
|
|
||||||
## Bug Reporting
|
## Bug Reporting
|
||||||
The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting).
|
The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](https://forums.eveonline.com/t/27156) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/pyfa-org/Pyfa/wiki/Bug-Reporting).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
Pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
* Development repository: [https://github.com/pyfa-org/Pyfa](https://github.com/pyfa-org/Pyfa)
|
* [Development repository](https://github.com/pyfa-org/Pyfa)
|
||||||
* [EVE forum thread](https://forums.eveonline.com/t/27156)
|
* [EVE forum thread](https://forums.eveonline.com/t/27156)
|
||||||
* [EVE University guide using pyfa](http://wiki.eveuniversity.org/Guide_to_using_PYFA)
|
* [EVE University guide using pyfa](https://wiki.eveuniversity.org/PYFA)
|
||||||
* [EVE Online website](http://www.eveonline.com/)
|
* [EVE Online website](http://www.eveonline.com/)
|
||||||
|
|
||||||
## Contacts:
|
## Contacts:
|
||||||
* Sable Blitzmann
|
|
||||||
* GitHub: @blitzmann
|
|
||||||
* [TweetFleet Slack](https://www.fuzzwork.co.uk/tweetfleet-slack-invites/): @blitzmann
|
|
||||||
* [Gitter chat](https://gitter.im/pyfa-org/Pyfa): @ blitzmann
|
|
||||||
* Email: sable.blitzmann@gmail.com
|
|
||||||
* Kadesh / DarkPhoenix
|
* Kadesh / DarkPhoenix
|
||||||
* GitHub: @DarkFenX
|
* GitHub: @DarkFenX
|
||||||
* EVE: Kadesh Priestess
|
* EVE: Kadesh Priestess
|
||||||
* Email: phoenix@mail.ru
|
* Email: phoenix@mail.ru
|
||||||
|
* Sable Blitzmann
|
||||||
|
* GitHub: @blitzmann
|
||||||
|
* [TweetFleet Slack](https://www.fuzzwork.co.uk/tweetfleet-slack-invites/): @blitzmann
|
||||||
|
* [Gitter chat](https://gitter.im/pyfa-org/Pyfa): @blitzmann
|
||||||
|
* Email: sable.blitzmann@gmail.com
|
||||||
|
|
||||||
## CCP Copyright Notice
|
## CCP Copyright Notice
|
||||||
EVE Online, the EVE logo, EVE and all associated logos and designs are the intellectual property of CCP hf. All artwork, screenshots, characters, vehicles, storylines, world facts or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of CCP hf. EVE Online and the EVE logo are the registered trademarks of CCP hf. All rights are reserved worldwide. All other trademarks are the property of their respective owners. CCP hf. has granted permission to pyfa to use EVE Online and all associated logos and designs for promotional and information purposes on its website but does not endorse, and is not in any way affiliated with, pyfa. CCP is in no way responsible for the content on or functioning of this program, nor can it be liable for any damage arising from the use of this program.
|
EVE Online, the EVE logo, EVE and all associated logos and designs are the intellectual property of CCP hf. All artwork, screenshots, characters, vehicles, storylines, world facts or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of CCP hf. EVE Online and the EVE logo are the registered trademarks of CCP hf. All rights are reserved worldwide. All other trademarks are the property of their respective owners. CCP hf. has granted permission to pyfa to use EVE Online and all associated logos and designs for promotional and information purposes on its website but does not endorse, and is not in any way affiliated with, pyfa. CCP is in no way responsible for the content on or functioning of this program, nor can it be liable for any damage arising from the use of this program.
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ def DBInMemory_test():
|
|||||||
gamedata_version = gamedata_session.execute(
|
gamedata_version = gamedata_session.execute(
|
||||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
|
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Missing gamedata version.")
|
print("Missing gamedata version.")
|
||||||
gamedata_version = None
|
gamedata_version = None
|
||||||
@@ -72,7 +74,7 @@ def DBInMemory_test():
|
|||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
#from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
|
#from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
#from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, miscData, module, override, price, queries, skill, targetProfile, user
|
#from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, miscData, module, override, price, queries, skill, targetProfile, user
|
||||||
|
|
||||||
# If using in memory saveddata, you'll want to reflect it so the data structure is good.
|
# If using in memory saveddata, you'll want to reflect it so the data structure is good.
|
||||||
if saveddata_connectionstring == "sqlite:///:memory:":
|
if saveddata_connectionstring == "sqlite:///:memory:":
|
||||||
|
|||||||
15
config.py
15
config.py
@@ -33,11 +33,14 @@ pyfaPath = None
|
|||||||
savePath = None
|
savePath = None
|
||||||
saveDB = None
|
saveDB = None
|
||||||
gameDB = None
|
gameDB = None
|
||||||
|
imgsZIP = None
|
||||||
logPath = None
|
logPath = None
|
||||||
loggingLevel = None
|
loggingLevel = None
|
||||||
logging_setup = None
|
logging_setup = None
|
||||||
cipher = None
|
cipher = None
|
||||||
clientHash = None
|
clientHash = None
|
||||||
|
experimentalFeatures = None
|
||||||
|
version = None
|
||||||
|
|
||||||
ESI_CACHE = 'esi_cache'
|
ESI_CACHE = 'esi_cache'
|
||||||
|
|
||||||
@@ -96,11 +99,13 @@ def defPaths(customSavePath=None):
|
|||||||
global savePath
|
global savePath
|
||||||
global saveDB
|
global saveDB
|
||||||
global gameDB
|
global gameDB
|
||||||
|
global imgsZIP
|
||||||
global saveInRoot
|
global saveInRoot
|
||||||
global logPath
|
global logPath
|
||||||
global cipher
|
global cipher
|
||||||
global clientHash
|
global clientHash
|
||||||
global version
|
global version
|
||||||
|
global experimentalFeatures
|
||||||
|
|
||||||
pyfalog.debug("Configuring Pyfa")
|
pyfalog.debug("Configuring Pyfa")
|
||||||
|
|
||||||
@@ -155,6 +160,10 @@ def defPaths(customSavePath=None):
|
|||||||
if not gameDB:
|
if not gameDB:
|
||||||
gameDB = os.path.join(pyfaPath, "eve.db")
|
gameDB = os.path.join(pyfaPath, "eve.db")
|
||||||
|
|
||||||
|
imgsZIP = getattr(configforced, "imgsZIP", imgsZIP)
|
||||||
|
if not imgsZIP:
|
||||||
|
imgsZIP = os.path.join(pyfaPath, "imgs.zip")
|
||||||
|
|
||||||
if debug:
|
if debug:
|
||||||
logFile = "pyfa_debug.log"
|
logFile = "pyfa_debug.log"
|
||||||
else:
|
else:
|
||||||
@@ -162,6 +171,10 @@ def defPaths(customSavePath=None):
|
|||||||
|
|
||||||
logPath = os.path.join(savePath, logFile)
|
logPath = os.path.join(savePath, logFile)
|
||||||
|
|
||||||
|
experimentalFeatures = getattr(configforced, "experimentalFeatures", experimentalFeatures)
|
||||||
|
if experimentalFeatures is None:
|
||||||
|
experimentalFeatures = False
|
||||||
|
|
||||||
# DON'T MODIFY ANYTHING BELOW
|
# DON'T MODIFY ANYTHING BELOW
|
||||||
import eos.config
|
import eos.config
|
||||||
|
|
||||||
@@ -220,6 +233,8 @@ def defLogging():
|
|||||||
# reset=False,
|
# reset=False,
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
print("Critical error attempting to setup logging. Falling back to console only.")
|
print("Critical error attempting to setup logging. Falling back to console only.")
|
||||||
logging_setup = NestedSetup([
|
logging_setup = NestedSetup([
|
||||||
|
|||||||
556
db_update.py
Normal file
556
db_update.py
Normal file
@@ -0,0 +1,556 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#======================================================================
|
||||||
|
# Copyright (C) 2012 Diego Duclos
|
||||||
|
#
|
||||||
|
# This file is part of eos.
|
||||||
|
#
|
||||||
|
# eos is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of
|
||||||
|
# the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# eos is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#======================================================================
|
||||||
|
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
ROOT_DIR = os.path.realpath(os.path.dirname(__file__))
|
||||||
|
DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
|
||||||
|
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
|
||||||
|
if ROOT_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, ROOT_DIR)
|
||||||
|
GAMEDATA_SCHEMA_VERSION = 3
|
||||||
|
|
||||||
|
|
||||||
|
def db_needs_update():
|
||||||
|
"""True if needs, false if it does not, none if we cannot check it."""
|
||||||
|
try:
|
||||||
|
with open(os.path.join(JSON_DIR, 'phobos', 'metadata.json')) as f:
|
||||||
|
data_version = next((r['field_value'] for r in json.load(f) if r['field_name'] == 'client_build'))
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
# If we have no source data - return None; should not update in this case
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
if not os.path.isfile(DB_PATH):
|
||||||
|
print('Gamedata DB not found')
|
||||||
|
return True
|
||||||
|
db_data_version = None
|
||||||
|
db_schema_version = None
|
||||||
|
try:
|
||||||
|
db = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'client_build\'')
|
||||||
|
for row in cursor:
|
||||||
|
db_data_version = int(row[0])
|
||||||
|
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'schema_version\'')
|
||||||
|
for row in cursor:
|
||||||
|
db_schema_version = int(row[0])
|
||||||
|
cursor.close()
|
||||||
|
db.close()
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
print('Error when fetching gamedata DB metadata')
|
||||||
|
return True
|
||||||
|
if data_version != db_data_version:
|
||||||
|
print('Gamedata DB data version mismatch: needed {}, DB has {}'.format(data_version, db_data_version))
|
||||||
|
return True
|
||||||
|
if GAMEDATA_SCHEMA_VERSION != db_schema_version:
|
||||||
|
print('Gamedata DB schema version mismatch: needed {}, DB has {}'.format(GAMEDATA_SCHEMA_VERSION, db_schema_version))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_db():
|
||||||
|
|
||||||
|
print('Building gamedata DB...')
|
||||||
|
|
||||||
|
if os.path.isfile(DB_PATH):
|
||||||
|
os.remove(DB_PATH)
|
||||||
|
|
||||||
|
import eos.db
|
||||||
|
import eos.gamedata
|
||||||
|
|
||||||
|
# Create the database tables
|
||||||
|
eos.db.gamedata_meta.create_all()
|
||||||
|
|
||||||
|
def _readData(minerName, jsonName, keyIdName=None):
|
||||||
|
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
|
||||||
|
rawData = json.load(f)
|
||||||
|
if not keyIdName:
|
||||||
|
return rawData
|
||||||
|
# IDs in keys, rows in values
|
||||||
|
data = []
|
||||||
|
for k, v in rawData.items():
|
||||||
|
row = {}
|
||||||
|
row.update(v)
|
||||||
|
if keyIdName not in row:
|
||||||
|
row[keyIdName] = int(k)
|
||||||
|
data.append(row)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _addRows(data, cls, fieldMap=None):
|
||||||
|
if fieldMap is None:
|
||||||
|
fieldMap = {}
|
||||||
|
for row in data:
|
||||||
|
instance = cls()
|
||||||
|
for k, v in row.items():
|
||||||
|
if isinstance(v, str):
|
||||||
|
v = v.strip()
|
||||||
|
setattr(instance, fieldMap.get(k, k), v)
|
||||||
|
eos.db.gamedata_session.add(instance)
|
||||||
|
|
||||||
|
def processEveTypes():
|
||||||
|
print('processing evetypes')
|
||||||
|
data = _readData('fsd_lite', 'evetypes', keyIdName='typeID')
|
||||||
|
for row in data:
|
||||||
|
if (
|
||||||
|
# Apparently people really want Civilian modules available
|
||||||
|
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
||||||
|
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor')
|
||||||
|
):
|
||||||
|
row['published'] = True
|
||||||
|
elif row['typeName'].startswith('Limited Synth '):
|
||||||
|
row['published'] = False
|
||||||
|
|
||||||
|
newData = []
|
||||||
|
for row in data:
|
||||||
|
if (
|
||||||
|
row['published'] or
|
||||||
|
# group Ship Modifiers, for items like tactical t3 ship modes
|
||||||
|
row['groupID'] == 1306 or
|
||||||
|
# Micro Bombs (Fighters)
|
||||||
|
row['typeID'] in (41549, 41548, 41551, 41550) or
|
||||||
|
# Abyssal weather (environment)
|
||||||
|
row['groupID'] in (
|
||||||
|
1882,
|
||||||
|
1975,
|
||||||
|
1971,
|
||||||
|
# the "container" for the abyssal environments
|
||||||
|
1983)
|
||||||
|
):
|
||||||
|
newData.append(row)
|
||||||
|
|
||||||
|
_addRows(newData, eos.gamedata.Item)
|
||||||
|
return newData
|
||||||
|
|
||||||
|
def processEveGroups():
|
||||||
|
print('processing evegroups')
|
||||||
|
data = _readData('fsd_lite', 'evegroups', keyIdName='groupID')
|
||||||
|
_addRows(data, eos.gamedata.Group)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def processEveCategories():
|
||||||
|
print('processing evecategories')
|
||||||
|
data = _readData('fsd_lite', 'evecategories', keyIdName='categoryID')
|
||||||
|
_addRows(data, eos.gamedata.Category)
|
||||||
|
|
||||||
|
def processDogmaAttributes():
|
||||||
|
print('processing dogmaattributes')
|
||||||
|
data = _readData('fsd_binary', 'dogmaattributes', keyIdName='attributeID')
|
||||||
|
_addRows(data, eos.gamedata.AttributeInfo)
|
||||||
|
|
||||||
|
def processDogmaTypeAttributes(eveTypesData):
|
||||||
|
print('processing dogmatypeattributes')
|
||||||
|
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||||
|
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||||
|
newData = []
|
||||||
|
seenKeys = set()
|
||||||
|
|
||||||
|
def checkKey(key):
|
||||||
|
if key in seenKeys:
|
||||||
|
return False
|
||||||
|
seenKeys.add(key)
|
||||||
|
return True
|
||||||
|
|
||||||
|
for typeData in data:
|
||||||
|
if typeData['typeID'] not in eveTypeIds:
|
||||||
|
continue
|
||||||
|
for row in typeData.get('dogmaAttributes', ()):
|
||||||
|
row['typeID'] = typeData['typeID']
|
||||||
|
if checkKey((row['typeID'], row['attributeID'])):
|
||||||
|
newData.append(row)
|
||||||
|
for row in eveTypesData:
|
||||||
|
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
||||||
|
if attrName in row and checkKey((row['typeID'], attrId)):
|
||||||
|
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
||||||
|
|
||||||
|
_addRows(newData, eos.gamedata.Attribute)
|
||||||
|
return newData
|
||||||
|
|
||||||
|
def processDynamicItemAttributes():
|
||||||
|
print('processing dynamicitemattributes')
|
||||||
|
data = _readData('fsd_binary', 'dynamicitemattributes')
|
||||||
|
for mutaID, mutaData in data.items():
|
||||||
|
muta = eos.gamedata.DynamicItem()
|
||||||
|
muta.typeID = mutaID
|
||||||
|
muta.resultingTypeID = mutaData['inputOutputMapping'][0]['resultingType']
|
||||||
|
eos.db.gamedata_session.add(muta)
|
||||||
|
|
||||||
|
for x in mutaData['inputOutputMapping'][0]['applicableTypes']:
|
||||||
|
item = eos.gamedata.DynamicItemItem()
|
||||||
|
item.typeID = mutaID
|
||||||
|
item.applicableTypeID = x
|
||||||
|
eos.db.gamedata_session.add(item)
|
||||||
|
|
||||||
|
for attrID, attrData in mutaData['attributeIDs'].items():
|
||||||
|
attr = eos.gamedata.DynamicItemAttribute()
|
||||||
|
attr.typeID = mutaID
|
||||||
|
attr.attributeID = attrID
|
||||||
|
attr.min = attrData['min']
|
||||||
|
attr.max = attrData['max']
|
||||||
|
eos.db.gamedata_session.add(attr)
|
||||||
|
|
||||||
|
def processDogmaEffects():
|
||||||
|
print('processing dogmaeffects')
|
||||||
|
data = _readData('fsd_binary', 'dogmaeffects', keyIdName='effectID')
|
||||||
|
_addRows(data, eos.gamedata.Effect, fieldMap={'resistanceAttributeID': 'resistanceID'})
|
||||||
|
|
||||||
|
def processDogmaTypeEffects(eveTypesData):
|
||||||
|
print('processing dogmatypeeffects')
|
||||||
|
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||||
|
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||||
|
newData = []
|
||||||
|
for typeData in data:
|
||||||
|
if typeData['typeID'] not in eveTypeIds:
|
||||||
|
continue
|
||||||
|
for row in typeData.get('dogmaEffects', ()):
|
||||||
|
row['typeID'] = typeData['typeID']
|
||||||
|
newData.append(row)
|
||||||
|
_addRows(newData, eos.gamedata.ItemEffect)
|
||||||
|
return newData
|
||||||
|
|
||||||
|
def processDogmaUnits():
|
||||||
|
print('processing dogmaunits')
|
||||||
|
data = _readData('fsd_binary', 'dogmaunits', keyIdName='unitID')
|
||||||
|
_addRows(data, eos.gamedata.Unit, fieldMap={'name': 'unitName'})
|
||||||
|
|
||||||
|
def processMarketGroups():
|
||||||
|
print('processing marketgroups')
|
||||||
|
data = _readData('fsd_binary', 'marketgroups', keyIdName='marketGroupID')
|
||||||
|
_addRows(data, eos.gamedata.MarketGroup, fieldMap={'name': 'marketGroupName'})
|
||||||
|
|
||||||
|
def processMetaGroups():
|
||||||
|
print('processing metagroups')
|
||||||
|
data = _readData('fsd_binary', 'metagroups', keyIdName='metaGroupID')
|
||||||
|
_addRows(data, eos.gamedata.MetaGroup)
|
||||||
|
|
||||||
|
def processCloneGrades():
|
||||||
|
print('processing clonegrades')
|
||||||
|
data = _readData('fsd_lite', 'clonegrades')
|
||||||
|
|
||||||
|
newData = []
|
||||||
|
# December, 2017 - CCP decided to use only one set of skill levels for alpha clones. However, this is still
|
||||||
|
# represented in the data as a skillset per race. To ensure that all skills are the same, we store them in a way
|
||||||
|
# that we can check to make sure all races have the same skills, as well as skill levels
|
||||||
|
check = {}
|
||||||
|
for ID in data:
|
||||||
|
for skill in data[ID]['skills']:
|
||||||
|
newData.append({
|
||||||
|
'alphaCloneID': int(ID),
|
||||||
|
'alphaCloneName': 'Alpha Clone',
|
||||||
|
'typeID': skill['typeID'],
|
||||||
|
'level': skill['level']})
|
||||||
|
if ID not in check:
|
||||||
|
check[ID] = {}
|
||||||
|
check[ID][int(skill['typeID'])] = int(skill['level'])
|
||||||
|
if not functools.reduce(lambda a, b: a if a == b else False, [v for _, v in check.items()]):
|
||||||
|
raise Exception('Alpha Clones not all equal')
|
||||||
|
newData = [x for x in newData if x['alphaCloneID'] == 1]
|
||||||
|
if len(newData) == 0:
|
||||||
|
raise Exception('Alpha Clone processing failed')
|
||||||
|
|
||||||
|
tmp = []
|
||||||
|
for row in newData:
|
||||||
|
if row['alphaCloneID'] not in tmp:
|
||||||
|
cloneParent = eos.gamedata.AlphaClone()
|
||||||
|
setattr(cloneParent, 'alphaCloneID', row['alphaCloneID'])
|
||||||
|
setattr(cloneParent, 'alphaCloneName', row['alphaCloneName'])
|
||||||
|
eos.db.gamedata_session.add(cloneParent)
|
||||||
|
tmp.append(row['alphaCloneID'])
|
||||||
|
_addRows(newData, eos.gamedata.AlphaCloneSkill)
|
||||||
|
|
||||||
|
def processTraits():
|
||||||
|
print('processing traits')
|
||||||
|
data = _readData('phobos', 'traits')
|
||||||
|
|
||||||
|
def convertSection(sectionData):
|
||||||
|
sectionLines = []
|
||||||
|
headerText = '<b>{}</b>'.format(sectionData['header'])
|
||||||
|
sectionLines.append(headerText)
|
||||||
|
for bonusData in sectionData['bonuses']:
|
||||||
|
prefix = '{} '.format(bonusData['number']) if 'number' in bonusData else ''
|
||||||
|
bonusText = '{}{}'.format(prefix, bonusData['text'].replace('\u00B7', '\u2022 '))
|
||||||
|
sectionLines.append(bonusText)
|
||||||
|
sectionLine = '<br />\n'.join(sectionLines)
|
||||||
|
return sectionLine
|
||||||
|
|
||||||
|
newData = []
|
||||||
|
for row in data:
|
||||||
|
typeLines = []
|
||||||
|
typeId = row['typeID']
|
||||||
|
traitData = row['traits']
|
||||||
|
for skillData in sorted(traitData.get('skills', ()), key=lambda i: i['header']):
|
||||||
|
typeLines.append(convertSection(skillData))
|
||||||
|
if 'role' in traitData:
|
||||||
|
typeLines.append(convertSection(traitData['role']))
|
||||||
|
if 'misc' in traitData:
|
||||||
|
typeLines.append(convertSection(traitData['misc']))
|
||||||
|
traitLine = '<br />\n<br />\n'.join(typeLines)
|
||||||
|
newRow = {'typeID': typeId, 'traitText': traitLine}
|
||||||
|
newData.append(newRow)
|
||||||
|
|
||||||
|
_addRows(newData, eos.gamedata.Traits)
|
||||||
|
|
||||||
|
def processMetadata():
|
||||||
|
print('processing metadata')
|
||||||
|
data = _readData('phobos', 'metadata')
|
||||||
|
_addRows(data, eos.gamedata.MetaData)
|
||||||
|
|
||||||
|
def processReqSkills(eveTypesData):
|
||||||
|
print('processing requiredskillsfortypes')
|
||||||
|
|
||||||
|
def composeReqSkills(raw):
|
||||||
|
reqSkills = {}
|
||||||
|
for skillTypeID, skillLevel in raw.items():
|
||||||
|
reqSkills[int(skillTypeID)] = skillLevel
|
||||||
|
return reqSkills
|
||||||
|
|
||||||
|
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||||
|
data = _readData('fsd_binary', 'requiredskillsfortypes')
|
||||||
|
reqsByItem = {}
|
||||||
|
itemsByReq = {}
|
||||||
|
for typeID, skillreqData in data.items():
|
||||||
|
typeID = int(typeID)
|
||||||
|
if typeID not in eveTypeIds:
|
||||||
|
continue
|
||||||
|
for skillTypeID, skillLevel in composeReqSkills(skillreqData).items():
|
||||||
|
reqsByItem.setdefault(typeID, {})[skillTypeID] = skillLevel
|
||||||
|
itemsByReq.setdefault(skillTypeID, {})[typeID] = skillLevel
|
||||||
|
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
|
||||||
|
if item.typeID in reqsByItem:
|
||||||
|
item.reqskills = json.dumps(reqsByItem[item.typeID])
|
||||||
|
if item.typeID in itemsByReq:
|
||||||
|
item.requiredfor = json.dumps(itemsByReq[item.typeID])
|
||||||
|
|
||||||
|
def processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData):
|
||||||
|
print('finding item replacements')
|
||||||
|
|
||||||
|
def compareAttrs(attrs1, attrs2):
|
||||||
|
# Consider items as different if they have no attrs
|
||||||
|
if len(attrs1) == 0 and len(attrs2) == 0:
|
||||||
|
return False
|
||||||
|
if set(attrs1) != set(attrs2):
|
||||||
|
return False
|
||||||
|
if all(attrs1[aid] == attrs2[aid] for aid in attrs1):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
skillReqAttribs = {
|
||||||
|
182: 277,
|
||||||
|
183: 278,
|
||||||
|
184: 279,
|
||||||
|
1285: 1286,
|
||||||
|
1289: 1287,
|
||||||
|
1290: 1288}
|
||||||
|
skillReqAttribsFlat = set(skillReqAttribs.keys()).union(skillReqAttribs.values())
|
||||||
|
# Get data on type groups
|
||||||
|
# Format: {type ID: group ID}
|
||||||
|
typesGroups = {}
|
||||||
|
for row in eveTypesData:
|
||||||
|
typesGroups[row['typeID']] = row['groupID']
|
||||||
|
# Get data on item effects
|
||||||
|
# Format: {type ID: set(effect, IDs)}
|
||||||
|
typesEffects = {}
|
||||||
|
for row in dogmaTypeEffectsData:
|
||||||
|
typesEffects.setdefault(row['typeID'], set()).add(row['effectID'])
|
||||||
|
# Get data on type attributes
|
||||||
|
# Format: {type ID: {attribute ID: attribute value}}
|
||||||
|
typesNormalAttribs = {}
|
||||||
|
typesSkillAttribs = {}
|
||||||
|
for row in dogmaTypeAttributesData:
|
||||||
|
attributeID = row['attributeID']
|
||||||
|
if attributeID in skillReqAttribsFlat:
|
||||||
|
typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
|
||||||
|
typeSkillAttribs[row['attributeID']] = row['value']
|
||||||
|
# Ignore these attributes for comparison purposes
|
||||||
|
elif attributeID in (
|
||||||
|
# We do not need mass as it affects final ship stats only when carried by ship itself
|
||||||
|
# (and we're not going to replace ships), but it's wildly inconsistent for other items,
|
||||||
|
# which otherwise would be the same
|
||||||
|
4, # mass
|
||||||
|
124, # mainColor
|
||||||
|
162, # radius
|
||||||
|
422, # techLevel
|
||||||
|
633, # metaLevel
|
||||||
|
1692, # metaGroupID
|
||||||
|
1768 # typeColorScheme
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
typeNormalAttribs = typesNormalAttribs.setdefault(row['typeID'], {})
|
||||||
|
typeNormalAttribs[row['attributeID']] = row['value']
|
||||||
|
# Get data on skill requirements
|
||||||
|
# Format: {type ID: {skill type ID: skill level}}
|
||||||
|
typesSkillReqs = {}
|
||||||
|
for typeID, typeAttribs in typesSkillAttribs.items():
|
||||||
|
typeSkillAttribs = typesSkillAttribs.get(typeID, {})
|
||||||
|
if not typeSkillAttribs:
|
||||||
|
continue
|
||||||
|
typeSkillReqs = typesSkillReqs.setdefault(typeID, {})
|
||||||
|
for skillreqTypeAttr, skillreqLevelAttr in skillReqAttribs.items():
|
||||||
|
try:
|
||||||
|
skillType = int(typeSkillAttribs[skillreqTypeAttr])
|
||||||
|
skillLevel = int(typeSkillAttribs[skillreqLevelAttr])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
continue
|
||||||
|
typeSkillReqs[skillType] = skillLevel
|
||||||
|
# Format: {group ID: category ID}
|
||||||
|
groupCategories = {}
|
||||||
|
for row in eveGroupsData:
|
||||||
|
groupCategories[row['groupID']] = row['categoryID']
|
||||||
|
# As EVE affects various types mostly depending on their group or skill requirements,
|
||||||
|
# we're going to group various types up this way
|
||||||
|
# Format: {(group ID, frozenset(skillreq, type, IDs), frozenset(type, effect, IDs): [type ID, {attribute ID: attribute value}]}
|
||||||
|
groupedData = {}
|
||||||
|
for row in eveTypesData:
|
||||||
|
typeID = row['typeID']
|
||||||
|
# Ignore items outside of categories we need
|
||||||
|
if groupCategories[typesGroups[typeID]] not in (
|
||||||
|
6, # Ship
|
||||||
|
7, # Module
|
||||||
|
8, # Charge
|
||||||
|
18, # Drone
|
||||||
|
20, # Implant
|
||||||
|
22, # Deployable
|
||||||
|
23, # Starbase
|
||||||
|
32, # Subsystem
|
||||||
|
35, # Decryptors
|
||||||
|
65, # Structure
|
||||||
|
66, # Structure Module
|
||||||
|
87, # Fighter
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
typeAttribs = typesNormalAttribs.get(typeID, {})
|
||||||
|
# Ignore items w/o attributes
|
||||||
|
if not typeAttribs:
|
||||||
|
continue
|
||||||
|
# We need only skill types, not levels for keys
|
||||||
|
typeSkillreqs = frozenset(typesSkillReqs.get(typeID, {}))
|
||||||
|
typeGroup = typesGroups[typeID]
|
||||||
|
typeEffects = frozenset(typesEffects.get(typeID, ()))
|
||||||
|
groupData = groupedData.setdefault((typeGroup, typeSkillreqs, typeEffects), [])
|
||||||
|
groupData.append((typeID, typeAttribs))
|
||||||
|
# Format: {type ID: set(type IDs)}
|
||||||
|
replacements = {}
|
||||||
|
# Now, go through composed groups and for every item within it
|
||||||
|
# find items which are the same
|
||||||
|
for groupData in groupedData.values():
|
||||||
|
for type1, type2 in itertools.combinations(groupData, 2):
|
||||||
|
if compareAttrs(type1[1], type2[1]):
|
||||||
|
replacements.setdefault(type1[0], set()).add(type2[0])
|
||||||
|
replacements.setdefault(type2[0], set()).add(type1[0])
|
||||||
|
# Update DB session with data we generated
|
||||||
|
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
|
||||||
|
itemReplacements = replacements.get(item.typeID)
|
||||||
|
if itemReplacements is not None:
|
||||||
|
item.replacements = ','.join('{}'.format(tid) for tid in sorted(itemReplacements))
|
||||||
|
|
||||||
|
def processImplantSets(eveTypesData):
|
||||||
|
print('composing implant sets')
|
||||||
|
# Includes only implants which can be considered part of sets, not all implants
|
||||||
|
implant_groups = (300, 1730)
|
||||||
|
specials = {'Genolution': ('Genolution Core Augmentation', r'CA-\d+')}
|
||||||
|
implantSets = {}
|
||||||
|
for row in eveTypesData:
|
||||||
|
if not row.get('published'):
|
||||||
|
continue
|
||||||
|
if row.get('groupID') not in implant_groups:
|
||||||
|
continue
|
||||||
|
typeName = row.get('typeName', '')
|
||||||
|
# Regular sets matching
|
||||||
|
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
|
||||||
|
# Special set matching
|
||||||
|
for setHandle, (setName, implantPattern) in specials.items():
|
||||||
|
pattern = '(?P<set>{}) (?P<implant>{})'.format(setName, implantPattern)
|
||||||
|
m = re.match(pattern, typeName)
|
||||||
|
if m:
|
||||||
|
implantSets.setdefault((None, setHandle), set()).add(row['typeID'])
|
||||||
|
break
|
||||||
|
data = []
|
||||||
|
for (gradeName, setName), implants in implantSets.items():
|
||||||
|
if len(implants) < 2:
|
||||||
|
continue
|
||||||
|
implants = ','.join('{}'.format(tid) for tid in sorted(implants))
|
||||||
|
row = {'setName': setName, 'gradeName': gradeName, 'implants': implants}
|
||||||
|
data.append(row)
|
||||||
|
_addRows(data, eos.gamedata.ImplantSet)
|
||||||
|
|
||||||
|
eveTypesData = processEveTypes()
|
||||||
|
eveGroupsData = processEveGroups()
|
||||||
|
processEveCategories()
|
||||||
|
processDogmaAttributes()
|
||||||
|
dogmaTypeAttributesData = processDogmaTypeAttributes(eveTypesData)
|
||||||
|
processDynamicItemAttributes()
|
||||||
|
processDogmaEffects()
|
||||||
|
dogmaTypeEffectsData = processDogmaTypeEffects(eveTypesData)
|
||||||
|
processDogmaUnits()
|
||||||
|
processMarketGroups()
|
||||||
|
processMetaGroups()
|
||||||
|
processCloneGrades()
|
||||||
|
processTraits()
|
||||||
|
processMetadata()
|
||||||
|
|
||||||
|
eos.db.gamedata_session.flush()
|
||||||
|
processReqSkills(eveTypesData)
|
||||||
|
processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData)
|
||||||
|
processImplantSets(eveTypesData)
|
||||||
|
|
||||||
|
# Add schema version to prevent further updates
|
||||||
|
metadata_schema_version = eos.gamedata.MetaData()
|
||||||
|
metadata_schema_version.field_name = 'schema_version'
|
||||||
|
metadata_schema_version.field_value = GAMEDATA_SCHEMA_VERSION
|
||||||
|
eos.db.gamedata_session.add(metadata_schema_version)
|
||||||
|
|
||||||
|
eos.db.gamedata_session.flush()
|
||||||
|
|
||||||
|
# CCP still has 5 subsystems assigned to T3Cs, even though only 4 are available / usable. They probably have some
|
||||||
|
# old legacy requirement or assumption that makes it difficult for them to change this value in the data. But for
|
||||||
|
# pyfa, we can do it here as a post-processing step
|
||||||
|
for attr in eos.db.gamedata_session.query(eos.gamedata.Attribute).filter(eos.gamedata.Attribute.ID == 1367).all():
|
||||||
|
attr.value = 4.0
|
||||||
|
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(eos.gamedata.Item.name.like('%abyssal%')).all():
|
||||||
|
item.published = False
|
||||||
|
|
||||||
|
for x in [
|
||||||
|
30 # Apparel
|
||||||
|
]:
|
||||||
|
cat = eos.db.gamedata_session.query(eos.gamedata.Category).filter(eos.gamedata.Category.ID == x).first()
|
||||||
|
print ('Removing Category: {}'.format(cat.name))
|
||||||
|
eos.db.gamedata_session.delete(cat)
|
||||||
|
|
||||||
|
eos.db.gamedata_session.commit()
|
||||||
|
eos.db.gamedata_engine.execute('VACUUM')
|
||||||
|
|
||||||
|
print('done')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
update_db()
|
||||||
@@ -30,7 +30,8 @@ added_files = [
|
|||||||
|
|
||||||
import_these = [
|
import_these = [
|
||||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||||
|
'pkg_resources.py2_warn' # issue 2156
|
||||||
]
|
]
|
||||||
|
|
||||||
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
# This apes hook-matplotlib.backends.py, but REMOVES backends, all but
|
|
||||||
# the ones in the list below.
|
|
||||||
# Courtesy of https://github.com/bpteague/cytoflow/blob/70f9291/packaging/hook-matplotlib.backends.py
|
|
||||||
|
|
||||||
KEEP = ["WXAgg", "WX", "agg"]
|
|
||||||
|
|
||||||
from PyInstaller.compat import is_darwin
|
|
||||||
from PyInstaller.utils.hooks import (
|
|
||||||
eval_statement, exec_statement, logger)
|
|
||||||
|
|
||||||
|
|
||||||
def get_matplotlib_backend_module_names():
|
|
||||||
"""
|
|
||||||
List the names of all matplotlib backend modules importable under the
|
|
||||||
current Python installation.
|
|
||||||
Returns
|
|
||||||
----------
|
|
||||||
list
|
|
||||||
List of the fully-qualified names of all such modules.
|
|
||||||
"""
|
|
||||||
# Statement safely importing a single backend module.
|
|
||||||
import_statement = """
|
|
||||||
import os, sys
|
|
||||||
# Preserve stdout.
|
|
||||||
sys_stdout = sys.stdout
|
|
||||||
try:
|
|
||||||
# Redirect output printed by this importation to "/dev/null", preventing
|
|
||||||
# such output from being erroneously interpreted as an error.
|
|
||||||
with open(os.devnull, 'w') as dev_null:
|
|
||||||
sys.stdout = dev_null
|
|
||||||
__import__('%s')
|
|
||||||
# If this is an ImportError, print this exception's message without a traceback.
|
|
||||||
# ImportError messages are human-readable and require no additional context.
|
|
||||||
except ImportError as exc:
|
|
||||||
sys.stdout = sys_stdout
|
|
||||||
print(exc)
|
|
||||||
# Else, print this exception preceded by a traceback. traceback.print_exc()
|
|
||||||
# prints to stderr rather than stdout and must not be called here!
|
|
||||||
except Exception:
|
|
||||||
sys.stdout = sys_stdout
|
|
||||||
import traceback
|
|
||||||
print(traceback.format_exc())
|
|
||||||
"""
|
|
||||||
|
|
||||||
# List of the human-readable names of all available backends.
|
|
||||||
backend_names = eval_statement(
|
|
||||||
'import matplotlib; print(matplotlib.rcsetup.all_backends)')
|
|
||||||
|
|
||||||
# List of the fully-qualified names of all importable backend modules.
|
|
||||||
module_names = []
|
|
||||||
|
|
||||||
# If the current system is not OS X and the "CocoaAgg" backend is available,
|
|
||||||
# remove this backend from consideration. Attempting to import this backend
|
|
||||||
# on non-OS X systems halts the current subprocess without printing output
|
|
||||||
# or raising exceptions, preventing its reliable detection.
|
|
||||||
if not is_darwin and 'CocoaAgg' in backend_names:
|
|
||||||
backend_names.remove('CocoaAgg')
|
|
||||||
|
|
||||||
# For safety, attempt to import each backend in a unique subprocess.
|
|
||||||
for backend_name in backend_names:
|
|
||||||
if backend_name in KEEP:
|
|
||||||
continue
|
|
||||||
|
|
||||||
module_name = 'matplotlib.backends.backend_%s' % backend_name.lower()
|
|
||||||
stdout = exec_statement(import_statement % module_name)
|
|
||||||
|
|
||||||
# If no output was printed, this backend is importable.
|
|
||||||
if not stdout:
|
|
||||||
module_names.append(module_name)
|
|
||||||
logger.info(' Matplotlib backend "%s": removed' % backend_name)
|
|
||||||
|
|
||||||
return module_names
|
|
||||||
|
|
||||||
# Freeze all importable backends, as PyInstaller is unable to determine exactly
|
|
||||||
# which backends are required by the current program.
|
|
||||||
e=get_matplotlib_backend_module_names()
|
|
||||||
print(e)
|
|
||||||
excludedimports = e
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
|
||||||
<noInheritable/>
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="Microsoft.VC90.CRT"
|
|
||||||
version="9.0.21022.8"
|
|
||||||
processorArchitecture="x86"
|
|
||||||
publicKeyToken="1fc8b3b9a1e18e3b"/>
|
|
||||||
<file name="MSVCR90.DLL"/>
|
|
||||||
<file name="MSVCM90.DLL"/>
|
|
||||||
<file name="MSVCP90.DLL"/>
|
|
||||||
</assembly>
|
|
||||||
@@ -14,7 +14,7 @@ with open("version.yml", 'r') as file:
|
|||||||
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
||||||
|
|
||||||
os.environ["PYFA_VERSION"] = version
|
os.environ["PYFA_VERSION"] = version
|
||||||
iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine
|
iscc = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||||
|
|
||||||
source = os.path.join(os.environ["PYFA_DIST_DIR"], "pyfa")
|
source = os.path.join(os.environ["PYFA_DIST_DIR"], "pyfa")
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,6 @@
|
|||||||
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
||||||
#define MyAppExeName "pyfa.exe"
|
#define MyAppExeName "pyfa.exe"
|
||||||
|
|
||||||
; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup
|
|
||||||
#define MajorVersionFlag 2
|
|
||||||
#define MinorVersionFlag 0
|
|
||||||
|
|
||||||
#ifndef MyOutputFile
|
#ifndef MyOutputFile
|
||||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
||||||
#endif
|
#endif
|
||||||
@@ -30,7 +26,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
|
|
||||||
; NOTE: The value of AppId uniquely identifies this application.
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
; Do not use the same AppId value in installers for other applications.
|
; Do not use the same AppId value in installers for other applications.
|
||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
@@ -41,6 +36,9 @@ AppPublisher={#MyAppPublisher}
|
|||||||
AppPublisherURL={#MyAppURL}
|
AppPublisherURL={#MyAppURL}
|
||||||
AppSupportURL={#MyAppURL}
|
AppSupportURL={#MyAppURL}
|
||||||
AppUpdatesURL={#MyAppURL}
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
ArchitecturesAllowed=x64
|
||||||
|
ArchitecturesInstallIn64BitMode=x64
|
||||||
|
CloseApplications=yes
|
||||||
DefaultDirName={pf}\{#MyAppName}
|
DefaultDirName={pf}\{#MyAppName}
|
||||||
DefaultGroupName={#MyAppName}
|
DefaultGroupName={#MyAppName}
|
||||||
AllowNoIcons=yes
|
AllowNoIcons=yes
|
||||||
@@ -49,7 +47,6 @@ OutputDir={#MyOutputDir}
|
|||||||
OutputBaseFilename={#MyOutputFile}
|
OutputBaseFilename={#MyOutputFile}
|
||||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
CloseApplications=yes
|
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@@ -83,6 +80,7 @@ Type: files; Name: "{app}\*.pyc"
|
|||||||
|
|
||||||
[Code]
|
[Code]
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
function IsAppRunning(const FileName : string): Boolean;
|
function IsAppRunning(const FileName : string): Boolean;
|
||||||
var
|
var
|
||||||
FSWbemLocator: Variant;
|
FSWbemLocator: Variant;
|
||||||
@@ -99,6 +97,7 @@ begin
|
|||||||
FSWbemLocator := Unassigned;
|
FSWbemLocator := Unassigned;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
procedure RemoveFromVirtualStore;
|
procedure RemoveFromVirtualStore;
|
||||||
var
|
var
|
||||||
VirtualStore,FileName,FilePath:String;
|
VirtualStore,FileName,FilePath:String;
|
||||||
@@ -115,6 +114,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||||
begin
|
begin
|
||||||
if(IsAppRunning( 'pyfa.exe' )) then
|
if(IsAppRunning( 'pyfa.exe' )) then
|
||||||
@@ -127,54 +127,61 @@ begin
|
|||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function GetUninstallString: string;
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
function GetUninstallString(): String;
|
||||||
var
|
var
|
||||||
sUnInstPath: string;
|
sUnInstPath: String;
|
||||||
sUnInstallString: String;
|
sUnInstallString: String;
|
||||||
begin
|
begin
|
||||||
Result := '';
|
|
||||||
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1'); //Your App GUID/ID
|
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1'); //Your App GUID/ID
|
||||||
sUnInstallString := '';
|
sUnInstallString := '';
|
||||||
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
|
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||||
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
|
if not RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||||
|
if not RegQueryStringValue(HKLM32, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||||
|
RegQueryStringValue(HKCU32, sUnInstPath, 'UninstallString', sUnInstallString);
|
||||||
Result := sUnInstallString;
|
Result := sUnInstallString;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function IsUpgrade: Boolean;
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
function UnInstallOldVersion(): Integer;
|
||||||
|
var
|
||||||
|
sUnInstallString: String;
|
||||||
|
iResultCode: Integer;
|
||||||
|
begin
|
||||||
|
// Return Values:
|
||||||
|
// 1 - uninstall string is empty
|
||||||
|
// 2 - error executing the UnInstallString
|
||||||
|
// 3 - successfully executed the UnInstallString
|
||||||
|
|
||||||
|
// default return value
|
||||||
|
Result := 0;
|
||||||
|
|
||||||
|
// get the uninstall string of the old app
|
||||||
|
sUnInstallString := GetUninstallString();
|
||||||
|
if sUnInstallString <> '' then begin
|
||||||
|
sUnInstallString := RemoveQuotes(sUnInstallString);
|
||||||
|
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
|
||||||
|
Result := 3
|
||||||
|
else
|
||||||
|
Result := 2;
|
||||||
|
end else
|
||||||
|
Result := 1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
function IsUpgrade(): Boolean;
|
||||||
begin
|
begin
|
||||||
Result := (GetUninstallString() <> '');
|
Result := (GetUninstallString() <> '');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function InitializeSetup: Boolean;
|
/////////////////////////////////////////////////////////////////////
|
||||||
var
|
procedure CurStepChanged(CurStep: TSetupStep);
|
||||||
V: Integer;
|
|
||||||
iResultCode: Integer;
|
|
||||||
sUnInstallString: string;
|
|
||||||
iOldVersionMajor: Cardinal;
|
|
||||||
iOldVersionMinor: Cardinal;
|
|
||||||
begin
|
begin
|
||||||
Result := True; // in case when no previous version is found
|
if (CurStep=ssInstall) then
|
||||||
if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', 'UninstallString') then //Your App GUID/ID
|
|
||||||
begin
|
begin
|
||||||
RegQueryDWordValue(HKEY_LOCAL_MACHINE,
|
if (IsUpgrade()) then
|
||||||
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1',
|
|
||||||
'MajorVersion', iOldVersionMajor);
|
|
||||||
RegQueryDWordValue(HKEY_LOCAL_MACHINE,
|
|
||||||
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1',
|
|
||||||
'MinorVersion', iOldVersionMinor);
|
|
||||||
if (iOldVersionMajor < {#MajorVersionFlag}) or ((iOldVersionMajor = {#MajorVersionFlag}) and (iOldVersionMinor < {#MinorVersionFlag})) then // If old version with old structure is installed.
|
|
||||||
begin
|
begin
|
||||||
V := MsgBox(ExpandConstant('An old version of pyfa was detected. Due to recent changes in the application structure, you must uninstall the previous version first. This will not affect your user data (saved fittings, characters, etc.). Do you want to uninstall now?'), mbInformation, MB_YESNO); //Custom Message if App installed
|
UnInstallOldVersion();
|
||||||
if V = IDYES then
|
|
||||||
begin
|
|
||||||
sUnInstallString := GetUninstallString();
|
|
||||||
sUnInstallString := RemoveQuotes(sUnInstallString);
|
|
||||||
Exec(ExpandConstant(sUnInstallString), '', '', SW_SHOW, ewWaitUntilTerminated, iResultCode);
|
|
||||||
Result := True; //if you want to proceed after uninstall
|
|
||||||
//Exit; //if you want to quit after uninstall
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Result := False; //when older version present and not uninstalled
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
</requestedPrivileges>
|
</requestedPrivileges>
|
||||||
</security>
|
</security>
|
||||||
</trustInfo>
|
</trustInfo>
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
<application>
|
<application>
|
||||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ added_files = [
|
|||||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||||
('../../dist_assets/win/pyfa.ico', '.'),
|
('../../dist_assets/win/pyfa.ico', '.'),
|
||||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
|
||||||
(requests.certs.where(), '.'), # is this needed anymore?
|
(requests.certs.where(), '.'), # is this needed anymore?
|
||||||
('../../eve.db', '.'),
|
('../../eve.db', '.'),
|
||||||
('../../README.md', '.'),
|
('../../README.md', '.'),
|
||||||
@@ -30,7 +29,8 @@ added_files = [
|
|||||||
|
|
||||||
import_these = [
|
import_these = [
|
||||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||||
|
'pkg_resources.py2_warn' # issue 2156
|
||||||
]
|
]
|
||||||
|
|
||||||
# Walk directories that do dynamic importing
|
# Walk directories that do dynamic importing
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
# -*- mode: python -*-
|
|
||||||
|
|
||||||
# Note: This script is provided AS-IS for those that may be interested.
|
|
||||||
# pyfa does not currently support pyInstaller (or any other build process) 100% at the moment
|
|
||||||
|
|
||||||
# Command line to build:
|
|
||||||
# (Run from directory where pyfa.py and pyfa.spec lives.)
|
|
||||||
# c:\Python27\scripts\pyinstaller.exe --clean --noconfirm --windowed --upx-dir=.\scripts\upx.exe pyfa.spec
|
|
||||||
|
|
||||||
# Don't forget to change the path to where your pyfa.py and pyfa.spec lives
|
|
||||||
# pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
added_files = [
|
|
||||||
( 'imgs/gui/*.png', 'imgs/gui' ),
|
|
||||||
( 'imgs/gui/*.gif', 'imgs/gui' ),
|
|
||||||
( 'imgs/icons/*.png', 'imgs/icons' ),
|
|
||||||
( 'imgs/renders/*.png', 'imgs/renders' ),
|
|
||||||
( 'dist_assets/win/pyfa.ico', '.' ),
|
|
||||||
( 'dist_assets/cacert.pem', '.' ),
|
|
||||||
( 'eve.db', '.' ),
|
|
||||||
( 'README.md', '.' ),
|
|
||||||
( 'LICENSE', '.' ),
|
|
||||||
]
|
|
||||||
|
|
||||||
import_these = []
|
|
||||||
|
|
||||||
# Walk eos.effects and add all effects so we can import them properly
|
|
||||||
for root, folders, files in os.walk("eos/effects"):
|
|
||||||
for file_ in files:
|
|
||||||
if file_.endswith(".py") and not file_.startswith("_"):
|
|
||||||
mod_name = "{}.{}".format(
|
|
||||||
root.replace("/", "."),
|
|
||||||
file_.split(".py")[0],
|
|
||||||
)
|
|
||||||
import_these.append(mod_name)
|
|
||||||
|
|
||||||
a = Analysis(
|
|
||||||
['pyfa.py'],
|
|
||||||
pathex=['C:\\projects\\pyfa\\'],
|
|
||||||
binaries=[],
|
|
||||||
datas=added_files,
|
|
||||||
hiddenimports=import_these,
|
|
||||||
hookspath=[],
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=[],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
)
|
|
||||||
|
|
||||||
pyz = PYZ(
|
|
||||||
a.pure,
|
|
||||||
a.zipped_data,
|
|
||||||
cipher=block_cipher,
|
|
||||||
)
|
|
||||||
|
|
||||||
exe = EXE(pyz,
|
|
||||||
a.scripts,
|
|
||||||
exclude_binaries=True,
|
|
||||||
debug=True,
|
|
||||||
console=True,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
name='pyfa_debug',
|
|
||||||
icon='dist_assets/win/pyfa.ico',
|
|
||||||
onefile=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
coll = COLLECT(
|
|
||||||
exe,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
onefile=False,
|
|
||||||
name='pyfa_debug',
|
|
||||||
icon='dist_assets/win/pyfa.ico',
|
|
||||||
)
|
|
||||||
71
eos/calc.py
Normal file
71
eos/calc.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Copyright (C) 2019 Ryan Holmes
|
||||||
|
#
|
||||||
|
# This file is part of pyfa.
|
||||||
|
#
|
||||||
|
# pyfa is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pyfa is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||||
|
# as multipliers arrive in different form) in here to not make actual attribute
|
||||||
|
# calculations slower than they already are due to extra function calls
|
||||||
|
def calculateMultiplier(multipliers):
|
||||||
|
"""
|
||||||
|
multipliers: dictionary in format:
|
||||||
|
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
|
||||||
|
"""
|
||||||
|
val = 1
|
||||||
|
for penalizedMultipliers in multipliers.values():
|
||||||
|
# A quick explanation of how this works:
|
||||||
|
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
||||||
|
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
||||||
|
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
||||||
|
# 2: The most significant bonuses take the smallest penalty,
|
||||||
|
# This means we'll have to sort
|
||||||
|
abssort = lambda _val: -abs(_val - 1)
|
||||||
|
l1.sort(key=abssort)
|
||||||
|
l2.sort(key=abssort)
|
||||||
|
# 3: The first module doesn't get penalized at all
|
||||||
|
# Any module after the first takes penalties according to:
|
||||||
|
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
||||||
|
for l in (l1, l2):
|
||||||
|
for i in range(len(l)):
|
||||||
|
bonus = l[i]
|
||||||
|
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
||||||
|
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||||
|
if distance is None:
|
||||||
|
return 1
|
||||||
|
if srcFalloffRange > 0:
|
||||||
|
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
||||||
|
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
||||||
|
return 0
|
||||||
|
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||||
|
elif distance <= srcOptimalRange:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def calculateLockTime(srcScanRes, tgtSigRadius):
|
||||||
|
if not srcScanRes or not tgtSigRadius:
|
||||||
|
return None
|
||||||
|
return min(40000 / srcScanRes / math.asinh(tgtSigRadius) ** 2, 30 * 60)
|
||||||
@@ -148,6 +148,7 @@ class CapSimulator:
|
|||||||
stability_precision = self.stability_precision
|
stability_precision = self.stability_precision
|
||||||
period = self.period
|
period = self.period
|
||||||
|
|
||||||
|
activation = None
|
||||||
iterations = 0
|
iterations = 0
|
||||||
|
|
||||||
capCapacity = self.capacitorCapacity
|
capCapacity = self.capacitorCapacity
|
||||||
@@ -162,7 +163,12 @@ class CapSimulator:
|
|||||||
t_max = self.t_max
|
t_max = self.t_max
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
activation = pop(state)
|
# Nothing to pop - might happen when no mods are activated, or when
|
||||||
|
# only cap injectors are active (and are postponed by code below)
|
||||||
|
try:
|
||||||
|
activation = pop(state)
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
t_now, duration, capNeed, shot, clipSize, reloadTime, isInjector = activation
|
t_now, duration, capNeed, shot, clipSize, reloadTime, isInjector = activation
|
||||||
|
|
||||||
# Max time reached, stop simulation - we're stable
|
# Max time reached, stop simulation - we're stable
|
||||||
@@ -275,7 +281,8 @@ class CapSimulator:
|
|||||||
activation[3] = shot
|
activation[3] = shot
|
||||||
|
|
||||||
push(state, activation)
|
push(state, activation)
|
||||||
push(state, activation)
|
if activation is not None:
|
||||||
|
push(state, activation)
|
||||||
|
|
||||||
# update instance with relevant results.
|
# update instance with relevant results.
|
||||||
self.t = t_last
|
self.t = t_last
|
||||||
|
|||||||
@@ -90,9 +90,12 @@ class FittingHardpoint(IntEnum):
|
|||||||
|
|
||||||
@unique
|
@unique
|
||||||
class SpoolType(IntEnum):
|
class SpoolType(IntEnum):
|
||||||
SCALE = 0 # [0..1]
|
# Spool and cycle scale are different in case if max spool amount cannot
|
||||||
TIME = 1 # Expressed via time in seconds since spool up started
|
# be divided by spool step without remainder
|
||||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
SPOOL_SCALE = 0 # [0..1]
|
||||||
|
CYCLE_SCALE = 1 # [0..1]
|
||||||
|
TIME = 2 # Expressed via time in seconds since spool up started
|
||||||
|
CYCLES = 3 # Expressed in amount of cycles since spool up started
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
|
|||||||
@@ -17,24 +17,37 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from sqlalchemy import MetaData, create_engine
|
from sqlalchemy import MetaData, create_engine, event
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
|
|
||||||
from . import migration
|
from . import migration
|
||||||
from eos import config
|
from eos import config
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
pyfalog.info("Initializing database")
|
pyfalog.info("Initializing database")
|
||||||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
||||||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyException(Exception):
|
class ReadOnlyException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def re_fn(expr, item):
|
||||||
|
try:
|
||||||
|
reg = re.compile(expr, re.IGNORECASE)
|
||||||
|
except (SystemExit, KeyboardInterrupt):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
return reg.search(item) is not None
|
||||||
|
|
||||||
|
|
||||||
pyfalog.debug('Initializing gamedata')
|
pyfalog.debug('Initializing gamedata')
|
||||||
gamedata_connectionstring = config.gamedata_connectionstring
|
gamedata_connectionstring = config.gamedata_connectionstring
|
||||||
if callable(gamedata_connectionstring):
|
if callable(gamedata_connectionstring):
|
||||||
@@ -42,9 +55,26 @@ if callable(gamedata_connectionstring):
|
|||||||
else:
|
else:
|
||||||
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
|
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
|
||||||
|
|
||||||
|
|
||||||
|
@event.listens_for(gamedata_engine, 'connect')
|
||||||
|
def create_functions(dbapi_connection, connection_record):
|
||||||
|
dbapi_connection.create_function('regexp', 2, re_fn)
|
||||||
|
|
||||||
|
|
||||||
gamedata_meta = MetaData()
|
gamedata_meta = MetaData()
|
||||||
gamedata_meta.bind = gamedata_engine
|
gamedata_meta.bind = gamedata_engine
|
||||||
gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)()
|
GamedataSession = scoped_session(sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False))
|
||||||
|
gamedata_session = GamedataSession()
|
||||||
|
|
||||||
|
gamedata_sessions = {threading.get_ident(): gamedata_session}
|
||||||
|
|
||||||
|
|
||||||
|
def get_gamedata_session():
|
||||||
|
thread_id = threading.get_ident()
|
||||||
|
if thread_id not in gamedata_sessions:
|
||||||
|
gamedata_sessions[thread_id] = GamedataSession()
|
||||||
|
return gamedata_sessions[thread_id]
|
||||||
|
|
||||||
|
|
||||||
pyfalog.debug('Getting gamedata version')
|
pyfalog.debug('Getting gamedata version')
|
||||||
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
|
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
|
||||||
@@ -56,6 +86,8 @@ try:
|
|||||||
config.gamedata_date = gamedata_session.execute(
|
config.gamedata_date = gamedata_session.execute(
|
||||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'dump_time'"
|
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'dump_time'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pyfalog.warning("Missing gamedata version.")
|
pyfalog.warning("Missing gamedata version.")
|
||||||
pyfalog.critical(e)
|
pyfalog.critical(e)
|
||||||
@@ -82,10 +114,10 @@ sd_lock = threading.RLock()
|
|||||||
pyfalog.debug('Importing gamedata DB scheme')
|
pyfalog.debug('Importing gamedata DB scheme')
|
||||||
# Import all the definitions for all our database stuff
|
# Import all the definitions for all our database stuff
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
|
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes, implantSet
|
||||||
pyfalog.debug('Importing saveddata DB scheme')
|
pyfalog.debug('Importing saveddata DB scheme')
|
||||||
# noinspection PyPep8
|
# noinspection PyPep8
|
||||||
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \
|
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \
|
||||||
miscData, mutator, module, override, price, queries, skill, targetProfile, user
|
miscData, mutator, module, override, price, queries, skill, targetProfile, user
|
||||||
|
|
||||||
pyfalog.debug('Importing gamedata queries')
|
pyfalog.debug('Importing gamedata queries')
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
|
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
|
||||||
"item", "marketGroup", "metaGroup", "unit", "alphaClones"]
|
"item", "marketGroup", "metaGroup", "unit", "alphaClones", "implantSet"]
|
||||||
|
|||||||
33
eos/db/gamedata/implantSet.py
Normal file
33
eos/db/gamedata/implantSet.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# ===============================================================================
|
||||||
|
# Copyright (C) 2010 Diego Duclos
|
||||||
|
#
|
||||||
|
# This file is part of eos.
|
||||||
|
#
|
||||||
|
# eos is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# eos is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
from sqlalchemy import Column, String, Integer, Table
|
||||||
|
from sqlalchemy.orm import mapper, synonym
|
||||||
|
|
||||||
|
from eos.db import gamedata_meta
|
||||||
|
from eos.gamedata import ImplantSet
|
||||||
|
|
||||||
|
implant_set_table = Table("implantsets", gamedata_meta,
|
||||||
|
Column("setID", Integer, primary_key=True),
|
||||||
|
Column("setName", String),
|
||||||
|
Column("gradeName", String),
|
||||||
|
Column("implants", String))
|
||||||
|
|
||||||
|
mapper(ImplantSet, implant_set_table,
|
||||||
|
properties={"ID": synonym("setID")})
|
||||||
@@ -25,7 +25,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
|
|||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table
|
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table
|
||||||
from eos.db.gamedata.effect import typeeffects_table
|
from eos.db.gamedata.effect import typeeffects_table
|
||||||
from eos.gamedata import Attribute, DynamicItem, Effect, Group, Item, MetaType, Traits
|
from eos.gamedata import Attribute, DynamicItem, Effect, Group, Item, Traits, MetaGroup
|
||||||
|
|
||||||
items_table = Table("invtypes", gamedata_meta,
|
items_table = Table("invtypes", gamedata_meta,
|
||||||
Column("typeID", Integer, primary_key=True),
|
Column("typeID", Integer, primary_key=True),
|
||||||
@@ -33,17 +33,18 @@ items_table = Table("invtypes", gamedata_meta,
|
|||||||
Column("description", String),
|
Column("description", String),
|
||||||
Column("raceID", Integer),
|
Column("raceID", Integer),
|
||||||
Column("factionID", Integer),
|
Column("factionID", Integer),
|
||||||
Column("volume", Float),
|
|
||||||
Column("mass", Float),
|
|
||||||
Column("capacity", Float),
|
|
||||||
Column("published", Boolean),
|
Column("published", Boolean),
|
||||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||||
Column("iconID", Integer),
|
Column("iconID", Integer),
|
||||||
Column("graphicID", Integer),
|
Column("graphicID", Integer),
|
||||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
||||||
Column("replacements", String))
|
Column("metaLevel", Integer),
|
||||||
|
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID"), index=True),
|
||||||
|
Column("variationParentTypeID", Integer, ForeignKey("invtypes.typeID"), index=True),
|
||||||
|
Column("replacements", String),
|
||||||
|
Column("reqskills", String),
|
||||||
|
Column("requiredfor", String))
|
||||||
|
|
||||||
from .metaGroup import metatypes_table # noqa
|
|
||||||
from .traits import traits_table # noqa
|
from .traits import traits_table # noqa
|
||||||
|
|
||||||
mapper(Item, items_table,
|
mapper(Item, items_table,
|
||||||
@@ -51,9 +52,8 @@ mapper(Item, items_table,
|
|||||||
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
|
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
|
||||||
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
|
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
|
||||||
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
|
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
|
||||||
"metaGroup" : relation(MetaType,
|
"metaGroup" : relation(MetaGroup, backref=backref("items", cascade="all,delete")),
|
||||||
primaryjoin=metatypes_table.c.typeID == items_table.c.typeID,
|
"varParent" : relation(Item, backref=backref("varChildren", cascade="all,delete"), remote_side=items_table.c.typeID),
|
||||||
uselist=False),
|
|
||||||
"ID" : synonym("typeID"),
|
"ID" : synonym("typeID"),
|
||||||
"name" : synonym("typeName"),
|
"name" : synonym("typeName"),
|
||||||
"description" : deferred(items_table.c.description),
|
"description" : deferred(items_table.c.description),
|
||||||
@@ -64,7 +64,6 @@ mapper(Item, items_table,
|
|||||||
primaryjoin=dynamicApplicable_table.c.applicableTypeID == items_table.c.typeID,
|
primaryjoin=dynamicApplicable_table.c.applicableTypeID == items_table.c.typeID,
|
||||||
secondaryjoin=dynamicApplicable_table.c.typeID == DynamicItem.typeID,
|
secondaryjoin=dynamicApplicable_table.c.typeID == DynamicItem.typeID,
|
||||||
secondary=dynamicApplicable_table,
|
secondary=dynamicApplicable_table,
|
||||||
backref="applicableItems")
|
backref="applicableItems")})
|
||||||
})
|
|
||||||
|
|
||||||
Item.category = association_proxy("group", "category")
|
Item.category = association_proxy("group", "category")
|
||||||
|
|||||||
@@ -17,35 +17,17 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String
|
from sqlalchemy import Table, Column, Integer, String
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.orm import mapper, synonym
|
||||||
from sqlalchemy.orm import relation, mapper, synonym
|
|
||||||
|
|
||||||
from eos.db import gamedata_meta
|
from eos.db import gamedata_meta
|
||||||
from eos.db.gamedata.item import items_table
|
from eos.gamedata import MetaGroup
|
||||||
from eos.gamedata import Item, MetaGroup, MetaType
|
|
||||||
|
|
||||||
metagroups_table = Table("invmetagroups", gamedata_meta,
|
metagroups_table = Table("invmetagroups", gamedata_meta,
|
||||||
Column("metaGroupID", Integer, primary_key=True),
|
Column("metaGroupID", Integer, primary_key=True),
|
||||||
Column("metaGroupName", String))
|
Column("metaGroupName", String))
|
||||||
|
|
||||||
metatypes_table = Table("invmetatypes", gamedata_meta,
|
|
||||||
Column("typeID", Integer, ForeignKey("invtypes.typeID"), primary_key=True),
|
|
||||||
Column("parentTypeID", Integer, ForeignKey("invtypes.typeID")),
|
|
||||||
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID")))
|
|
||||||
|
|
||||||
mapper(MetaGroup, metagroups_table,
|
mapper(MetaGroup, metagroups_table,
|
||||||
properties={
|
properties={
|
||||||
"ID" : synonym("metaGroupID"),
|
"ID" : synonym("metaGroupID"),
|
||||||
"name": synonym("metaGroupName")
|
"name": synonym("metaGroupName")})
|
||||||
})
|
|
||||||
|
|
||||||
mapper(MetaType, metatypes_table,
|
|
||||||
properties={
|
|
||||||
"ID" : synonym("metaGroupID"),
|
|
||||||
"parent": relation(Item, primaryjoin=metatypes_table.c.parentTypeID == items_table.c.typeID),
|
|
||||||
"items" : relation(Item, primaryjoin=metatypes_table.c.typeID == items_table.c.typeID),
|
|
||||||
"info" : relation(MetaGroup, lazy=False)
|
|
||||||
})
|
|
||||||
|
|
||||||
MetaType.name = association_proxy("info", "name")
|
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ from sqlalchemy.orm import aliased, exc, join
|
|||||||
from sqlalchemy.sql import and_, or_, select
|
from sqlalchemy.sql import and_, or_, select
|
||||||
|
|
||||||
import eos.config
|
import eos.config
|
||||||
from eos.db import gamedata_session
|
from eos.db import get_gamedata_session
|
||||||
|
from eos.db.gamedata.item import items_table
|
||||||
from eos.db.gamedata.group import groups_table
|
from eos.db.gamedata.group import groups_table
|
||||||
from eos.db.gamedata.metaGroup import items_table, metatypes_table
|
|
||||||
from eos.db.util import processEager, processWhere
|
from eos.db.util import processEager, processWhere
|
||||||
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup
|
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup, ImplantSet
|
||||||
|
|
||||||
cache = {}
|
cache = {}
|
||||||
configVal = getattr(eos.config, "gamedataCache", None)
|
configVal = getattr(eos.config, "gamedataCache", None)
|
||||||
@@ -64,7 +64,7 @@ else:
|
|||||||
return deco
|
return deco
|
||||||
|
|
||||||
|
|
||||||
def sqlizeString(line):
|
def sqlizeNormalString(line):
|
||||||
# Escape backslashes first, as they will be as escape symbol in queries
|
# Escape backslashes first, as they will be as escape symbol in queries
|
||||||
# Then escape percent and underscore signs
|
# Then escape percent and underscore signs
|
||||||
# Finally, replace generic wildcards with sql-style wildcards
|
# Finally, replace generic wildcards with sql-style wildcards
|
||||||
@@ -79,28 +79,39 @@ itemNameMap = {}
|
|||||||
def getItem(lookfor, eager=None):
|
def getItem(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(Item).get(lookfor)
|
item = get_gamedata_session().query(Item).get(lookfor)
|
||||||
else:
|
else:
|
||||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in itemNameMap:
|
if lookfor in itemNameMap:
|
||||||
id = itemNameMap[lookfor]
|
id = itemNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(Item).get(id)
|
item = get_gamedata_session().query(Item).get(id)
|
||||||
else:
|
else:
|
||||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# Item names are unique, so we can use first() instead of one()
|
# Item names are unique, so we can use first() instead of one()
|
||||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
||||||
itemNameMap[lookfor] = item.ID
|
if item is not None:
|
||||||
|
itemNameMap[lookfor] = item.ID
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@cachedQuery(1, "itemIDs")
|
||||||
|
def getItems(itemIDs, eager=None):
|
||||||
|
if not isinstance(itemIDs, (tuple, list, set)) or not all(isinstance(t, int) for t in itemIDs):
|
||||||
|
raise TypeError("Need iterable of integers as argument")
|
||||||
|
if eager is None:
|
||||||
|
items = get_gamedata_session().query(Item).filter(Item.ID.in_(itemIDs)).all()
|
||||||
|
else:
|
||||||
|
items = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID.in_(itemIDs)).all()
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
def getMutaplasmid(lookfor, eager=None):
|
def getMutaplasmid(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
item = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
return item
|
return item
|
||||||
@@ -108,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
|
|||||||
|
|
||||||
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||||
# A lot of this is described in more detail in #1597
|
# A lot of this is described in more detail in #1597
|
||||||
item = gamedata_session.query(Item).get(lookfor)
|
item = get_gamedata_session().query(Item).get(lookfor)
|
||||||
base = getItem(baseItemID)
|
base = getItem(baseItemID)
|
||||||
|
|
||||||
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
||||||
@@ -124,7 +135,7 @@ def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
|||||||
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
|
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
|
||||||
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
|
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
|
||||||
# we want to generate a completely new object to work with
|
# we want to generate a completely new object to work with
|
||||||
gamedata_session.expunge(item)
|
get_gamedata_session().expunge(item)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
@@ -147,7 +158,7 @@ def getItems(lookfor, eager=None):
|
|||||||
|
|
||||||
if len(toGet) > 0:
|
if len(toGet) > 0:
|
||||||
# Get items that aren't currently cached, and store them in the cache
|
# Get items that aren't currently cached, and store them in the cache
|
||||||
items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all()
|
items = get_gamedata_session().query(Item).filter(Item.ID.in_(toGet)).all()
|
||||||
for item in items:
|
for item in items:
|
||||||
cache[(item.ID, None)] = item
|
cache[(item.ID, None)] = item
|
||||||
results += items
|
results += items
|
||||||
@@ -161,9 +172,9 @@ def getItems(lookfor, eager=None):
|
|||||||
def getAlphaClone(lookfor, eager=None):
|
def getAlphaClone(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
item = gamedata_session.query(AlphaClone).get(lookfor)
|
item = get_gamedata_session().query(AlphaClone).get(lookfor)
|
||||||
else:
|
else:
|
||||||
item = gamedata_session.query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
item = get_gamedata_session().query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
return item
|
return item
|
||||||
@@ -171,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
|
|||||||
|
|
||||||
def getAlphaCloneList(eager=None):
|
def getAlphaCloneList(eager=None):
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
clones = gamedata_session.query(AlphaClone).options(*eager).all()
|
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
|
||||||
return clones
|
return clones
|
||||||
|
|
||||||
|
|
||||||
@@ -182,20 +193,21 @@ groupNameMap = {}
|
|||||||
def getGroup(lookfor, eager=None):
|
def getGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
group = gamedata_session.query(Group).get(lookfor)
|
group = get_gamedata_session().query(Group).get(lookfor)
|
||||||
else:
|
else:
|
||||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in groupNameMap:
|
if lookfor in groupNameMap:
|
||||||
id = groupNameMap[lookfor]
|
id = groupNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
group = gamedata_session.query(Group).get(id)
|
group = get_gamedata_session().query(Group).get(id)
|
||||||
else:
|
else:
|
||||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# Group names are unique, so we can use first() instead of one()
|
# Group names are unique, so we can use first() instead of one()
|
||||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
||||||
groupNameMap[lookfor] = group.ID
|
if group is not None:
|
||||||
|
groupNameMap[lookfor] = group.ID
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return group
|
return group
|
||||||
@@ -208,23 +220,24 @@ categoryNameMap = {}
|
|||||||
def getCategory(lookfor, eager=None):
|
def getCategory(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
category = gamedata_session.query(Category).get(lookfor)
|
category = get_gamedata_session().query(Category).get(lookfor)
|
||||||
else:
|
else:
|
||||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||||
Category.ID == lookfor).first()
|
Category.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in categoryNameMap:
|
if lookfor in categoryNameMap:
|
||||||
id = categoryNameMap[lookfor]
|
id = categoryNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
category = gamedata_session.query(Category).get(id)
|
category = get_gamedata_session().query(Category).get(id)
|
||||||
else:
|
else:
|
||||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||||
Category.ID == id).first()
|
Category.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# Category names are unique, so we can use first() instead of one()
|
# Category names are unique, so we can use first() instead of one()
|
||||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||||
Category.name == lookfor).first()
|
Category.name == lookfor).first()
|
||||||
categoryNameMap[lookfor] = category.ID
|
if category is not None:
|
||||||
|
categoryNameMap[lookfor] = category.ID
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return category
|
return category
|
||||||
@@ -237,35 +250,40 @@ metaGroupNameMap = {}
|
|||||||
def getMetaGroup(lookfor, eager=None):
|
def getMetaGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
|
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
|
||||||
else:
|
else:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||||
MetaGroup.ID == lookfor).first()
|
MetaGroup.ID == lookfor).first()
|
||||||
elif isinstance(lookfor, str):
|
elif isinstance(lookfor, str):
|
||||||
if lookfor in metaGroupNameMap:
|
if lookfor in metaGroupNameMap:
|
||||||
id = metaGroupNameMap[lookfor]
|
id = metaGroupNameMap[lookfor]
|
||||||
if eager is None:
|
if eager is None:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).get(id)
|
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
|
||||||
else:
|
else:
|
||||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||||
MetaGroup.ID == id).first()
|
MetaGroup.ID == id).first()
|
||||||
else:
|
else:
|
||||||
# MetaGroup names are unique, so we can use first() instead of one()
|
# MetaGroup names are unique, so we can use first() instead of one()
|
||||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||||
MetaGroup.name == lookfor).first()
|
MetaGroup.name == lookfor).first()
|
||||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
if metaGroup is not None:
|
||||||
|
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return metaGroup
|
return metaGroup
|
||||||
|
|
||||||
|
|
||||||
|
def getMetaGroups():
|
||||||
|
return get_gamedata_session().query(MetaGroup).all()
|
||||||
|
|
||||||
|
|
||||||
@cachedQuery(1, "lookfor")
|
@cachedQuery(1, "lookfor")
|
||||||
def getMarketGroup(lookfor, eager=None):
|
def getMarketGroup(lookfor, eager=None):
|
||||||
if isinstance(lookfor, int):
|
if isinstance(lookfor, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
|
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
|
||||||
else:
|
else:
|
||||||
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
|
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
|
||||||
MarketGroup.ID == lookfor).first()
|
MarketGroup.ID == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
@@ -277,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
|
|||||||
addedIds = set(rootNodeIds)
|
addedIds = set(rootNodeIds)
|
||||||
while addedIds:
|
while addedIds:
|
||||||
allIds.update(addedIds)
|
allIds.update(addedIds)
|
||||||
addedIds = {mg.ID for mg in gamedata_session.query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
addedIds = {mg.ID for mg in get_gamedata_session().query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||||
return allIds
|
return allIds
|
||||||
|
|
||||||
|
|
||||||
@@ -291,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
|
|||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
|
|
||||||
filter = processWhere(filter, where)
|
filter = processWhere(filter, where)
|
||||||
return gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
return get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
||||||
filter).all()
|
filter).all()
|
||||||
|
|
||||||
|
|
||||||
@@ -306,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
|||||||
if not hasattr(join, "__iter__"):
|
if not hasattr(join, "__iter__"):
|
||||||
join = (join,)
|
join = (join,)
|
||||||
|
|
||||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(*join)
|
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||||
for token in nameLike.split(' '):
|
for token in nameLike.split(' '):
|
||||||
token_safe = "%{0}%".format(sqlizeString(token))
|
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
||||||
else:
|
else:
|
||||||
@@ -317,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
|||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
@cachedQuery(3, "tokens", "where", "join")
|
||||||
|
def searchItemsRegex(tokens, where=None, join=None, eager=None):
|
||||||
|
if not isinstance(tokens, (tuple, list)) or not all(isinstance(t, str) for t in tokens):
|
||||||
|
raise TypeError("Need tuple or list of strings as argument")
|
||||||
|
|
||||||
|
if join is None:
|
||||||
|
join = tuple()
|
||||||
|
|
||||||
|
if not hasattr(join, "__iter__"):
|
||||||
|
join = (join,)
|
||||||
|
|
||||||
|
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||||
|
for token in tokens:
|
||||||
|
if where is not None:
|
||||||
|
items = items.filter(and_(Item.name.op('regexp')(token), where))
|
||||||
|
else:
|
||||||
|
items = items.filter(Item.name.op('regexp')(token))
|
||||||
|
items = items.limit(100).all()
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
@cachedQuery(3, "where", "nameLike", "join")
|
@cachedQuery(3, "where", "nameLike", "join")
|
||||||
def searchSkills(nameLike, where=None, eager=None):
|
def searchSkills(nameLike, where=None, eager=None):
|
||||||
if not isinstance(nameLike, str):
|
if not isinstance(nameLike, str):
|
||||||
raise TypeError("Need string as argument")
|
raise TypeError("Need string as argument")
|
||||||
|
|
||||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
||||||
for token in nameLike.split(' '):
|
for token in nameLike.split(' '):
|
||||||
token_safe = "%{0}%".format(sqlizeString(token))
|
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||||
if where is not None:
|
if where is not None:
|
||||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
||||||
else:
|
else:
|
||||||
@@ -342,11 +381,9 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
|||||||
if len(itemids) == 0:
|
if len(itemids) == 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
itemfilter = or_(*(metatypes_table.c.parentTypeID == itemid for itemid in itemids))
|
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||||
filter = processWhere(itemfilter, where)
|
filter = processWhere(itemfilter, where)
|
||||||
joinon = items_table.c.typeID == metatypes_table.c.typeID
|
vars = get_gamedata_session().query(Item).options(*processEager(eager)).filter(filter).all()
|
||||||
vars = gamedata_session.query(Item).options(*processEager(eager)).join((metatypes_table, joinon)).filter(
|
|
||||||
filter).all()
|
|
||||||
|
|
||||||
if vars:
|
if vars:
|
||||||
return vars
|
return vars
|
||||||
@@ -354,7 +391,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
|||||||
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
||||||
filter = processWhere(itemfilter, where)
|
filter = processWhere(itemfilter, where)
|
||||||
joinon = items_table.c.groupID == groups_table.c.groupID
|
joinon = items_table.c.groupID == groups_table.c.groupID
|
||||||
vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
vars = get_gamedata_session().query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
||||||
filter).all()
|
filter).all()
|
||||||
|
|
||||||
return vars
|
return vars
|
||||||
@@ -369,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
|
|||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
try:
|
try:
|
||||||
result = gamedata_session.query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
result = get_gamedata_session().query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
@@ -378,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
|
|||||||
@cachedQuery(1, "field")
|
@cachedQuery(1, "field")
|
||||||
def getMetaData(field):
|
def getMetaData(field):
|
||||||
if isinstance(field, str):
|
if isinstance(field, str):
|
||||||
data = gamedata_session.query(MetaData).get(field)
|
data = get_gamedata_session().query(MetaData).get(field)
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need string as argument")
|
raise TypeError("Need string as argument")
|
||||||
return data
|
return data
|
||||||
@@ -397,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
|
|||||||
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
||||||
from_obj=[join(Attribute, Item)])
|
from_obj=[join(Attribute, Item)])
|
||||||
|
|
||||||
result = gamedata_session.execute(q).fetchall()
|
result = get_gamedata_session().execute(q).fetchall()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def getAbyssalTypes():
|
def getAbyssalTypes():
|
||||||
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
|
return set([r.resultingTypeID for r in get_gamedata_session().query(DynamicItem.resultingTypeID).distinct()])
|
||||||
|
|
||||||
|
|
||||||
@cachedQuery(1, "itemID")
|
@cachedQuery(1, "itemID")
|
||||||
@@ -410,9 +447,9 @@ def getDynamicItem(itemID, eager=None):
|
|||||||
try:
|
try:
|
||||||
if isinstance(itemID, int):
|
if isinstance(itemID, int):
|
||||||
if eager is None:
|
if eager is None:
|
||||||
result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
result = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
||||||
else:
|
else:
|
||||||
result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
result = get_gamedata_session().query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer as argument")
|
raise TypeError("Need integer as argument")
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
@@ -420,23 +457,7 @@ def getDynamicItem(itemID, eager=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def getRequiredFor(itemID, attrMapping):
|
@cachedQuery(1, "lookfor")
|
||||||
Attribute1 = aliased(Attribute)
|
def getAllImplantSets():
|
||||||
Attribute2 = aliased(Attribute)
|
implantSets = get_gamedata_session().query(ImplantSet).all()
|
||||||
|
return implantSets
|
||||||
skillToLevelClauses = []
|
|
||||||
|
|
||||||
for attrSkill, attrLevel in attrMapping.items():
|
|
||||||
skillToLevelClauses.append(and_(Attribute1.attributeID == attrSkill, Attribute2.attributeID == attrLevel))
|
|
||||||
|
|
||||||
queryOr = or_(*skillToLevelClauses)
|
|
||||||
|
|
||||||
q = select((Attribute2.typeID, Attribute2.value),
|
|
||||||
and_(Attribute1.value == itemID, queryOr),
|
|
||||||
from_obj=[
|
|
||||||
join(Attribute1, Attribute2, Attribute1.typeID == Attribute2.typeID)
|
|
||||||
])
|
|
||||||
|
|
||||||
result = gamedata_session.execute(q).fetchall()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -8,43 +8,25 @@ many upgrade files as there are database versions (version 5 would include
|
|||||||
upgrade files 1-5)
|
upgrade files 1-5)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pkgutil
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from eos.utils.pyinst_support import iterNamespace
|
||||||
|
|
||||||
updates = {}
|
updates = {}
|
||||||
appVersion = 0
|
appVersion = 0
|
||||||
|
|
||||||
prefix = __name__ + "."
|
prefix = __name__ + "."
|
||||||
|
|
||||||
# load modules to work based with and without pyinstaller
|
for modName in iterNamespace(__name__, __path__):
|
||||||
# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
|
|
||||||
# see: https://github.com/pyinstaller/pyinstaller/issues/1905
|
|
||||||
|
|
||||||
# load modules using iter_modules()
|
|
||||||
# (should find all filters in normal build, but not pyinstaller)
|
|
||||||
module_names = [m[1] for m in pkgutil.iter_modules(__path__, prefix)]
|
|
||||||
|
|
||||||
# special handling for PyInstaller
|
|
||||||
importers = map(pkgutil.get_importer, __path__)
|
|
||||||
toc = set()
|
|
||||||
for i in importers:
|
|
||||||
if hasattr(i, 'toc'):
|
|
||||||
toc |= i.toc
|
|
||||||
|
|
||||||
for elm in toc:
|
|
||||||
if elm.startswith(prefix):
|
|
||||||
module_names.append(elm)
|
|
||||||
|
|
||||||
for modname in module_names:
|
|
||||||
# loop through python files, extracting update number and function, and
|
# loop through python files, extracting update number and function, and
|
||||||
# adding it to a list
|
# adding it to a list
|
||||||
modname_tail = modname.rsplit('.', 1)[-1]
|
modname_tail = modName.rsplit('.', 1)[-1]
|
||||||
module = __import__(modname, fromlist=True)
|
|
||||||
m = re.match("^upgrade(?P<index>\d+)$", modname_tail)
|
m = re.match("^upgrade(?P<index>\d+)$", modname_tail)
|
||||||
if not m:
|
if not m:
|
||||||
continue
|
continue
|
||||||
index = int(m.group("index"))
|
index = int(m.group("index"))
|
||||||
appVersion = max(appVersion, index)
|
appVersion = max(appVersion, index)
|
||||||
|
module = __import__(modName, fromlist=True)
|
||||||
upgrade = getattr(module, "upgrade", False)
|
upgrade = getattr(module, "upgrade", False)
|
||||||
if upgrade:
|
if upgrade:
|
||||||
updates[index] = upgrade
|
updates[index] = upgrade
|
||||||
|
|||||||
@@ -33,9 +33,13 @@ def upgrade(saveddata_engine):
|
|||||||
try:
|
try:
|
||||||
saveddata_session.execute(commandFits_table.insert(),
|
saveddata_session.execute(commandFits_table.insert(),
|
||||||
{"boosterID": value, "boostedID": boosted, "active": 1})
|
{"boosterID": value, "boostedID": boosted, "active": 1})
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
saveddata_session.commit()
|
saveddata_session.commit()
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# Shouldn't fail unless you have updated database without the old fleet schema and manually modify the database version
|
# Shouldn't fail unless you have updated database without the old fleet schema and manually modify the database version
|
||||||
# If it does, simply fail. Fleet data migration isn't critically important here
|
# If it does, simply fail. Fleet data migration isn't critically important here
|
||||||
|
|||||||
@@ -4235,6 +4235,8 @@ def upgrade(saveddata_engine):
|
|||||||
|
|
||||||
# And last but not least, delete the last subsystem
|
# And last but not least, delete the last subsystem
|
||||||
saveddata_engine.execute("DELETE FROM modules WHERE ID = ?", (oldModules[4][0],))
|
saveddata_engine.execute("DELETE FROM modules WHERE ID = ?", (oldModules[4][0],))
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# if something fails, fuck it, we tried. It'll default to the generic conversion below
|
# if something fails, fuck it, we tried. It'll default to the generic conversion below
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ Allow use of floats in damage pattern values
|
|||||||
|
|
||||||
tmpTable = """
|
tmpTable = """
|
||||||
CREATE TABLE "damagePatternsTemp" (
|
CREATE TABLE "damagePatternsTemp" (
|
||||||
"ID" INTEGER NOT NULL,
|
"ID" INTEGER NOT NULL,
|
||||||
"name" VARCHAR,
|
"name" VARCHAR,
|
||||||
"emAmount" FLOAT,
|
"emAmount" FLOAT,
|
||||||
"thermalAmount" FLOAT,
|
"thermalAmount" FLOAT,
|
||||||
"kineticAmount" FLOAT,
|
"kineticAmount" FLOAT,
|
||||||
"explosiveAmount" FLOAT,
|
"explosiveAmount" FLOAT,
|
||||||
"ownerID" INTEGER,
|
"ownerID" INTEGER,
|
||||||
"created" DATETIME,
|
"created" DATETIME,
|
||||||
"modified" DATETIME,
|
"modified" DATETIME,
|
||||||
PRIMARY KEY ("ID"),
|
PRIMARY KEY ("ID"),
|
||||||
FOREIGN KEY("ownerID") REFERENCES users ("ID")
|
FOREIGN KEY("ownerID") REFERENCES users ("ID")
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|||||||
25
eos/db/migrations/upgrade34.py
Normal file
25
eos/db/migrations/upgrade34.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
Migration 34
|
||||||
|
|
||||||
|
- Adds projection range columns to projectable entities
|
||||||
|
"""
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT projectionRange FROM projectedFits LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN projectionRange FLOAT;")
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT projectionRange FROM modules LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN projectionRange FLOAT;")
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT projectionRange FROM drones LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE drones ADD COLUMN projectionRange FLOAT;")
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT projectionRange FROM fighters LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE fighters ADD COLUMN projectionRange FLOAT;")
|
||||||
166
eos/db/migrations/upgrade35.py
Normal file
166
eos/db/migrations/upgrade35.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
"""
|
||||||
|
Migration 35
|
||||||
|
|
||||||
|
- Remove builtin damage patterns and target profiles from the database
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
dmgPatterns = (
|
||||||
|
'Uniform',
|
||||||
|
'[Bombs]Concussion Bomb',
|
||||||
|
'[Bombs]Electron Bomb',
|
||||||
|
'[Bombs]Scorch Bomb',
|
||||||
|
'[Bombs]Shrapnel Bomb',
|
||||||
|
'[Exotic Plasma]Baryon',
|
||||||
|
'[Exotic Plasma]Meson',
|
||||||
|
'[Exotic Plasma]Tetryon',
|
||||||
|
'[Exotic Plasma][T2] Mystic',
|
||||||
|
'[Exotic Plasma][T2] Occult',
|
||||||
|
'[Frequency Crystals]Gamma',
|
||||||
|
'[Frequency Crystals]Infrared',
|
||||||
|
'[Frequency Crystals]Microwave',
|
||||||
|
'[Frequency Crystals]Multifrequency',
|
||||||
|
'[Frequency Crystals]Radio',
|
||||||
|
'[Frequency Crystals]Standard',
|
||||||
|
'[Frequency Crystals]Ultraviolet',
|
||||||
|
'[Frequency Crystals]Xray',
|
||||||
|
'[Frequency Crystals][T2] Aurora',
|
||||||
|
'[Frequency Crystals][T2] Conflagration',
|
||||||
|
'[Frequency Crystals][T2] Gleam',
|
||||||
|
'[Frequency Crystals][T2] Scorch',
|
||||||
|
'[Generic]EM',
|
||||||
|
'[Generic]Explosive',
|
||||||
|
'[Generic]Kinetic',
|
||||||
|
'[Generic]Thermal',
|
||||||
|
'[Hybrid Charges]Antimatter',
|
||||||
|
'[Hybrid Charges]Iridium',
|
||||||
|
'[Hybrid Charges]Iron',
|
||||||
|
'[Hybrid Charges]Lead',
|
||||||
|
'[Hybrid Charges]Plutonium',
|
||||||
|
'[Hybrid Charges]Thorium',
|
||||||
|
'[Hybrid Charges]Tungsten',
|
||||||
|
'[Hybrid Charges]Uranium',
|
||||||
|
'[Hybrid Charges][T2] Javelin',
|
||||||
|
'[Hybrid Charges][T2] Null',
|
||||||
|
'[Hybrid Charges][T2] Spike',
|
||||||
|
'[Hybrid Charges][T2] Void',
|
||||||
|
'[Missiles]Inferno',
|
||||||
|
'[Missiles]Mjolnir',
|
||||||
|
'[Missiles]Nova',
|
||||||
|
'[Missiles]Scourge',
|
||||||
|
'[Missiles][Structure) Standup Missile',
|
||||||
|
'[Missiles][Structure] Standup Missile',
|
||||||
|
'[NPC][Asteroid] Angel Cartel',
|
||||||
|
'[NPC][Asteroid] Blood Raiders',
|
||||||
|
'[NPC][Asteroid] Guristas',
|
||||||
|
'[NPC][Asteroid] Rogue Drone',
|
||||||
|
'[NPC][Asteroid] Sanshas Nation',
|
||||||
|
'[NPC][Asteroid] Serpentis',
|
||||||
|
'[NPC][Burner] Ashimmu (Blood Raiders)',
|
||||||
|
'[NPC][Burner] Cruor (Blood Raiders)',
|
||||||
|
'[NPC][Burner] Daredevil (Serpentis)',
|
||||||
|
'[NPC][Burner] Dramiel (Angel)',
|
||||||
|
'[NPC][Burner] Enyo',
|
||||||
|
'[NPC][Burner] Hawk',
|
||||||
|
'[NPC][Burner] Jaguar',
|
||||||
|
'[NPC][Burner] Sentinel',
|
||||||
|
'[NPC][Burner] Succubus (Sanshas Nation)',
|
||||||
|
'[NPC][Burner] Talos',
|
||||||
|
'[NPC][Burner] Vengeance',
|
||||||
|
'[NPC][Burner] Worm (Guristas)',
|
||||||
|
'[NPC][Deadspace] Angel Cartel',
|
||||||
|
'[NPC][Deadspace] Blood Raiders',
|
||||||
|
'[NPC][Deadspace] Guristas',
|
||||||
|
'[NPC][Deadspace] Rogue Drone',
|
||||||
|
'[NPC][Deadspace] Sanshas Nation',
|
||||||
|
'[NPC][Deadspace] Serpentis',
|
||||||
|
'[NPC][Mission] Amarr Empire',
|
||||||
|
'[NPC][Mission] CONCORD',
|
||||||
|
'[NPC][Mission] Caldari State',
|
||||||
|
'[NPC][Mission] Gallente Federation',
|
||||||
|
'[NPC][Mission] Khanid',
|
||||||
|
'[NPC][Mission] Minmatar Republic',
|
||||||
|
'[NPC][Mission] Mordus Legion',
|
||||||
|
'[NPC][Mission] Thukker',
|
||||||
|
'[NPC][Other] Sansha Incursion',
|
||||||
|
'[NPC][Other] Sleepers',
|
||||||
|
'[Projectile Ammo]Carbonized Lead',
|
||||||
|
'[Projectile Ammo]Depleted Uranium',
|
||||||
|
'[Projectile Ammo]EMP',
|
||||||
|
'[Projectile Ammo]Fusion',
|
||||||
|
'[Projectile Ammo]Nuclear',
|
||||||
|
'[Projectile Ammo]Phased Plasma',
|
||||||
|
'[Projectile Ammo]Proton',
|
||||||
|
'[Projectile Ammo]Titanium Sabot',
|
||||||
|
'[Projectile Ammo][T2] Barrage',
|
||||||
|
'[Projectile Ammo][T2] Hail',
|
||||||
|
'[Projectile Ammo][T2] Quake',
|
||||||
|
'[Projectile Ammo][T2] Tremor')
|
||||||
|
|
||||||
|
tgtProfiles = (
|
||||||
|
'Uniform (25%)',
|
||||||
|
'Uniform (50%)',
|
||||||
|
'Uniform (75%)',
|
||||||
|
'Uniform (90%)',
|
||||||
|
'[NPC][Asteroid] Angel Cartel',
|
||||||
|
'[NPC][Asteroid] Blood Raiders',
|
||||||
|
'[NPC][Asteroid] Guristas',
|
||||||
|
'[NPC][Asteroid] Rogue Drones',
|
||||||
|
'[NPC][Asteroid] Sanshas Nation',
|
||||||
|
'[NPC][Asteroid] Serpentis',
|
||||||
|
'[NPC][Burner] Ashimmu (Blood Raiders)',
|
||||||
|
'[NPC][Burner] Cruor (Blood Raiders)',
|
||||||
|
'[NPC][Burner] Daredevil (Serpentis)',
|
||||||
|
'[NPC][Burner] Dramiel (Angel)',
|
||||||
|
'[NPC][Burner] Enyo',
|
||||||
|
'[NPC][Burner] Hawk',
|
||||||
|
'[NPC][Burner] Jaguar',
|
||||||
|
'[NPC][Burner] Sentinel',
|
||||||
|
'[NPC][Burner] Succubus (Sanshas Nation)',
|
||||||
|
'[NPC][Burner] Talos',
|
||||||
|
'[NPC][Burner] Vengeance',
|
||||||
|
'[NPC][Burner] Worm (Guristas)',
|
||||||
|
'[NPC][Deadspace] Angel Cartel',
|
||||||
|
'[NPC][Deadspace] Blood Raiders',
|
||||||
|
'[NPC][Deadspace] Guristas',
|
||||||
|
'[NPC][Deadspace] Rogue Drones',
|
||||||
|
'[NPC][Deadspace] Sanshas Nation',
|
||||||
|
'[NPC][Deadspace] Serpentis',
|
||||||
|
'[NPC][Mission] Amarr Empire',
|
||||||
|
'[NPC][Mission] CONCORD',
|
||||||
|
'[NPC][Mission] Caldari State',
|
||||||
|
'[NPC][Mission] Gallente Federation',
|
||||||
|
'[NPC][Mission] Khanid',
|
||||||
|
'[NPC][Mission] Minmatar Republic',
|
||||||
|
'[NPC][Mission] Mordus Legion',
|
||||||
|
'[NPC][Other] Sansha Incursion',
|
||||||
|
'[NPC][Other] Sleeper',
|
||||||
|
'[T1 Resist]Armor',
|
||||||
|
'[T1 Resist]Armor (+T2 DCU)',
|
||||||
|
'[T1 Resist]Hull',
|
||||||
|
'[T1 Resist]Hull (+T2 DCU)',
|
||||||
|
'[T1 Resist]Shield',
|
||||||
|
'[T1 Resist]Shield (+T2 DCU)',
|
||||||
|
'[T2 Resist]Amarr (Armor)',
|
||||||
|
'[T2 Resist]Amarr (Shield)',
|
||||||
|
'[T2 Resist]Caldari (Armor)',
|
||||||
|
'[T2 Resist]Caldari (Shield)',
|
||||||
|
'[T2 Resist]Gallente (Armor)',
|
||||||
|
'[T2 Resist]Gallente (Shield)',
|
||||||
|
'[T2 Resist]Minmatar (Armor)',
|
||||||
|
'[T2 Resist]Minmatar (Shield)')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
saveddata_engine.execute('DELETE FROM damagePatterns WHERE name in ({});'.format(', '.join('\'{}\''.format(n) for n in dmgPatterns)))
|
||||||
|
saveddata_engine.execute('DELETE FROM targetResists WHERE name in ({});'.format(', '.join('\'{}\''.format(n) for n in tgtProfiles)))
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT builtinDamagePatternID FROM fits LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN builtinDamagePatternID INT;")
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT builtinTargetResistsID FROM fits LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN builtinTargetResistsID INT;")
|
||||||
84
eos/db/migrations/upgrade36.py
Normal file
84
eos/db/migrations/upgrade36.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
Migration 36
|
||||||
|
|
||||||
|
- Shield Booster, Armor Repairer and Capacitor Transfer tiericide
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
6441: ( # Small Clarity Ward Enduring Shield Booster
|
||||||
|
6443, # Small Converse Deflection Catalyzer
|
||||||
|
),
|
||||||
|
6437: ( # Small C5-L Compact Shield Booster
|
||||||
|
6439, # Small Neutron Saturation Injector I
|
||||||
|
),
|
||||||
|
10868: ( # Medium Clarity Ward Enduring Shield Booster
|
||||||
|
10870, # Medium Converse Deflection Catalyzer
|
||||||
|
),
|
||||||
|
10872: ( # Medium C5-L Compact Shield Booster
|
||||||
|
10866, # Medium Neutron Saturation Injector I
|
||||||
|
),
|
||||||
|
10876: ( # Large Clarity Ward Enduring Shield Booster
|
||||||
|
10878, # Large Converse Deflection Catalyzer
|
||||||
|
),
|
||||||
|
10880: ( # Large C5-L Compact Shield Booster
|
||||||
|
10874, # Large Neutron Saturation Injector I
|
||||||
|
),
|
||||||
|
10884: ( # X-Large Clarity Ward Enduring Shield Booster
|
||||||
|
10886, # X-Large Converse Deflection Catalyzer
|
||||||
|
),
|
||||||
|
10888: ( # X-Large C5-L Compact Shield Booster
|
||||||
|
10882, # X-Large Neutron Saturation Injector I
|
||||||
|
),
|
||||||
|
4533: ( # Small ACM Compact Armor Repairer
|
||||||
|
4531, # Small Inefficient Armor Repair Unit
|
||||||
|
),
|
||||||
|
4529: ( # Small I-a Enduring Armor Repairer
|
||||||
|
4535, # Small Automated Carapace Restoration
|
||||||
|
),
|
||||||
|
4573: ( # Medium ACM Compact Armor Repairer
|
||||||
|
4571, # Medium Inefficient Armor Repair Unit
|
||||||
|
),
|
||||||
|
4569: ( # Medium I-a Enduring Armor Repairer
|
||||||
|
4575, # Medium Automated Carapace Restoration
|
||||||
|
),
|
||||||
|
22889: ( # 'Meditation' Medium Armor Repairer I
|
||||||
|
4579, # Medium Nano Armor Repair Unit I
|
||||||
|
),
|
||||||
|
4613: ( # Large ACM Compact Armor Repairer
|
||||||
|
4611, # Large Inefficient Armor Repair Unit
|
||||||
|
),
|
||||||
|
4609: ( # Large I-a Enduring Armor Repairer
|
||||||
|
4615, # Large Automated Carapace Restoration
|
||||||
|
),
|
||||||
|
22891: ( # 'Protest' Large Armor Repairer I
|
||||||
|
4621, # Large 'Reprieve' Vestment Reconstructer I
|
||||||
|
),
|
||||||
|
5093: ( # Small Radiative Scoped Remote Capacitor Transmitter
|
||||||
|
5087, # Small Partial E95a Remote Capacitor Transmitter
|
||||||
|
),
|
||||||
|
5091: ( # Small Inductive Compact Remote Capacitor Transmitter
|
||||||
|
5089, # Small Murky Remote Capacitor Transmitter
|
||||||
|
),
|
||||||
|
16489: ( # Medium Radiative Scoped Remote Capacitor Transmitter
|
||||||
|
16493, # Medium Partial E95b Remote Capacitor Transmitter
|
||||||
|
),
|
||||||
|
16495: ( # Medium Inductive Compact Remote Capacitor Transmitter
|
||||||
|
16491, # Medium Murky Remote Capacitor Transmitter
|
||||||
|
),
|
||||||
|
16481: ( # Large Radiative Scoped Remote Capacitor Transmitter
|
||||||
|
16485, # Large Partial E95c Remote Capacitor Transmitter
|
||||||
|
),
|
||||||
|
16487: ( # Large Inductive Compact Remote Capacitor Transmitter
|
||||||
|
16483, # Large Murky Remote Capacitor Transmitter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
# Convert modules
|
||||||
|
for replacement_item, list in CONVERSIONS.items():
|
||||||
|
for retired_item in list:
|
||||||
|
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
|
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
44
eos/db/migrations/upgrade37.py
Normal file
44
eos/db/migrations/upgrade37.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
"""
|
||||||
|
Migration 37
|
||||||
|
|
||||||
|
- Capacitor Booster tiericide
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
4959: ( # 'Seed' Micro Capacitor Booster I
|
||||||
|
4957, # Micro Brief Capacitor Overcharge I
|
||||||
|
4961, # Micro Tapered Capacitor Infusion I
|
||||||
|
4955, # Micro F-RX Prototype Capacitor Boost
|
||||||
|
3556, # Micro Capacitor Booster I
|
||||||
|
3558, # Micro Capacitor Booster II
|
||||||
|
15774, # Ammatar Navy Micro Capacitor Booster
|
||||||
|
14180, # Dark Blood Micro Capacitor Booster
|
||||||
|
14182, # True Sansha Micro Capacitor Booster
|
||||||
|
15782, # Imperial Navy Micro Capacitor Booster
|
||||||
|
),
|
||||||
|
5011: ( # Small F-RX Compact Capacitor Booster
|
||||||
|
5009, # Small Brief Capacitor Overcharge I
|
||||||
|
5013, # Small Tapered Capacitor Infusion I
|
||||||
|
5007, # Small F-RX Prototype Capacitor Boost
|
||||||
|
),
|
||||||
|
4833: ( # Medium F-RX Compact Capacitor Booster
|
||||||
|
4831, # Medium Brief Capacitor Overcharge I
|
||||||
|
4835, # Medium Tapered Capacitor Infusion I
|
||||||
|
4829, # Medium F-RX Prototype Capacitor Boost
|
||||||
|
),
|
||||||
|
5051: ( # Heavy F-RX Compact Capacitor Booster
|
||||||
|
5049, # Heavy Brief Capacitor Overcharge I
|
||||||
|
5053, # Heavy Tapered Capacitor Infusion I
|
||||||
|
5047, # Heavy F-RX Prototype Capacitor Boost
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
# Convert modules
|
||||||
|
for replacement_item, list in CONVERSIONS.items():
|
||||||
|
for retired_item in list:
|
||||||
|
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
|
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
42
eos/db/migrations/upgrade38.py
Normal file
42
eos/db/migrations/upgrade38.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""
|
||||||
|
Migration 38
|
||||||
|
|
||||||
|
- Armor hardener tiericide
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
16357: ( # Experimental Enduring EM Armor Hardener I
|
||||||
|
16353, # Upgraded Armor EM Hardener I
|
||||||
|
),
|
||||||
|
16365: ( # Experimental Enduring Explosive Armor Hardener I
|
||||||
|
16361, # Upgraded Armor Explosive Hardener I
|
||||||
|
),
|
||||||
|
16373: ( # Experimental Enduring Kinetic Armor Hardener I
|
||||||
|
16369, # Upgraded Armor Kinetic Hardener I
|
||||||
|
),
|
||||||
|
16381: ( # Experimental Enduring Thermal Armor Hardener I
|
||||||
|
16377, # Upgraded Armor Thermal Hardener I
|
||||||
|
),
|
||||||
|
16359: ( # Prototype Compact EM Armor Hardener I
|
||||||
|
16355, # Limited Armor EM Hardener I
|
||||||
|
),
|
||||||
|
16367: ( # Prototype Compact Explosive Armor Hardener I
|
||||||
|
16363, # Limited Armor Explosive Hardener I
|
||||||
|
),
|
||||||
|
16375: ( # Prototype Compact Kinetic Armor Hardener I
|
||||||
|
16371, # Limited Armor Kinetic Hardener I
|
||||||
|
),
|
||||||
|
16383: ( # Prototype Compact Thermal Armor Hardener I
|
||||||
|
16379, # Limited Armor Thermal Hardener I
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
# Convert modules
|
||||||
|
for replacement_item, list in CONVERSIONS.items():
|
||||||
|
for retired_item in list:
|
||||||
|
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
|
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
30
eos/db/migrations/upgrade39.py
Normal file
30
eos/db/migrations/upgrade39.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"""
|
||||||
|
Migration 39
|
||||||
|
|
||||||
|
- Shield amplifier tiericide
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONVERSIONS = {
|
||||||
|
1798: ( # 'Basic' EM Shield Amplifier
|
||||||
|
9562, # Supplemental EM Ward Amplifier
|
||||||
|
),
|
||||||
|
1804: ( # 'Basic' Explosive Shield Amplifier
|
||||||
|
9574, # Supplemental Explosive Deflection Amplifier
|
||||||
|
),
|
||||||
|
1802: ( # 'Basic' Kinetic Shield Amplifier
|
||||||
|
9570, # Supplemental Kinetic Deflection Amplifier
|
||||||
|
),
|
||||||
|
1800: ( # 'Basic' Thermal Shield Amplifier
|
||||||
|
9566, # Supplemental Thermal Dissipation Amplifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
# Convert modules
|
||||||
|
for replacement_item, list in CONVERSIONS.items():
|
||||||
|
for retired_item in list:
|
||||||
|
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
|
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||||
|
(replacement_item, retired_item))
|
||||||
@@ -13,6 +13,5 @@ __all__ = [
|
|||||||
"miscData",
|
"miscData",
|
||||||
"targetProfile",
|
"targetProfile",
|
||||||
"override",
|
"override",
|
||||||
"implantSet",
|
"implantSet"
|
||||||
"loadDefaultDatabaseValues"
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -24,16 +24,20 @@ import datetime
|
|||||||
from eos.db import saveddata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.saveddata.damagePattern import DamagePattern
|
from eos.saveddata.damagePattern import DamagePattern
|
||||||
|
|
||||||
damagePatterns_table = Table("damagePatterns", saveddata_meta,
|
damagePatterns_table = Table(
|
||||||
Column("ID", Integer, primary_key=True),
|
'damagePatterns',
|
||||||
Column("name", String),
|
saveddata_meta,
|
||||||
Column("emAmount", Float),
|
Column('ID', Integer, primary_key=True),
|
||||||
Column("thermalAmount", Float),
|
Column('name', String),
|
||||||
Column("kineticAmount", Float),
|
Column('emAmount', Float),
|
||||||
Column("explosiveAmount", Float),
|
Column('thermalAmount', Float),
|
||||||
Column("ownerID", ForeignKey("users.ID"), nullable=True),
|
Column('kineticAmount', Float),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column('explosiveAmount', Float),
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
Column('ownerID', ForeignKey('users.ID'), nullable=True),
|
||||||
)
|
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
|
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
|
||||||
|
|
||||||
mapper(DamagePattern, damagePatterns_table)
|
mapper(
|
||||||
|
DamagePattern,
|
||||||
|
damagePatterns_table,
|
||||||
|
properties={'rawName': damagePatterns_table.c.name})
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||||
from sqlalchemy.orm import mapper, relation
|
from sqlalchemy.orm import mapper, relation
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@@ -33,7 +33,8 @@ drones_table = Table("drones", saveddata_meta,
|
|||||||
Column("amountActive", Integer, nullable=False),
|
Column("amountActive", Integer, nullable=False),
|
||||||
Column("projected", Boolean, default=False),
|
Column("projected", Boolean, default=False),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||||
|
Column("projectionRange", Float, nullable=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
mapper(Drone, drones_table,
|
mapper(Drone, drones_table,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||||
from sqlalchemy.orm import mapper, relation
|
from sqlalchemy.orm import mapper, relation
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@ fighters_table = Table("fighters", saveddata_meta,
|
|||||||
Column("amount", Integer, nullable=False),
|
Column("amount", Integer, nullable=False),
|
||||||
Column("projected", Boolean, default=False),
|
Column("projected", Boolean, default=False),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||||
|
Column("projectionRange", Float, nullable=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
fighter_abilities_table = Table("fightersAbilities", saveddata_meta,
|
fighter_abilities_table = Table("fightersAbilities", saveddata_meta,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
|
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Float, String, Table
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
|
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
|
||||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
@@ -53,8 +53,10 @@ fits_table = Table("fits", saveddata_meta,
|
|||||||
Column("timestamp", Integer, nullable=False),
|
Column("timestamp", Integer, nullable=False),
|
||||||
Column("characterID", ForeignKey("characters.ID"), nullable=True),
|
Column("characterID", ForeignKey("characters.ID"), nullable=True),
|
||||||
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
|
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
|
||||||
|
Column("builtinDamagePatternID", Integer, nullable=True),
|
||||||
Column("booster", Boolean, nullable=False, index=True, default=0),
|
Column("booster", Boolean, nullable=False, index=True, default=0),
|
||||||
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
||||||
|
Column("builtinTargetResistsID", Integer, nullable=True),
|
||||||
Column("modeID", Integer, nullable=True),
|
Column("modeID", Integer, nullable=True),
|
||||||
Column("implantLocation", Integer, nullable=False),
|
Column("implantLocation", Integer, nullable=False),
|
||||||
Column("notes", String, nullable=True),
|
Column("notes", String, nullable=True),
|
||||||
@@ -70,7 +72,8 @@ projectedFits_table = Table("projectedFits", saveddata_meta,
|
|||||||
Column("amount", Integer, nullable=False, default=1),
|
Column("amount", Integer, nullable=False, default=1),
|
||||||
Column("active", Boolean, nullable=False, default=1),
|
Column("active", Boolean, nullable=False, default=1),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||||
|
Column("projectionRange", Float, nullable=True),
|
||||||
)
|
)
|
||||||
|
|
||||||
commandFits_table = Table("commandFits", saveddata_meta,
|
commandFits_table = Table("commandFits", saveddata_meta,
|
||||||
@@ -83,6 +86,7 @@ commandFits_table = Table("commandFits", saveddata_meta,
|
|||||||
|
|
||||||
|
|
||||||
class ProjectedFit:
|
class ProjectedFit:
|
||||||
|
|
||||||
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
||||||
self.sourceID = sourceID
|
self.sourceID = sourceID
|
||||||
self.source_fit = source_fit
|
self.source_fit = source_fit
|
||||||
@@ -231,8 +235,10 @@ mapper(es_Fit, fits_table,
|
|||||||
"_Fit__character": relation(
|
"_Fit__character": relation(
|
||||||
Character,
|
Character,
|
||||||
backref="fits"),
|
backref="fits"),
|
||||||
"_Fit__damagePattern": relation(DamagePattern),
|
"_Fit__userDamagePattern": relation(DamagePattern),
|
||||||
"_Fit__targetProfile": relation(TargetProfile),
|
"_Fit__builtinDamagePatternID": fits_table.c.builtinDamagePatternID,
|
||||||
|
"_Fit__userTargetProfile": relation(TargetProfile),
|
||||||
|
"_Fit__builtinTargetProfileID": fits_table.c.builtinTargetResistsID,
|
||||||
"projectedOnto": projectedFitSourceRel,
|
"projectedOnto": projectedFitSourceRel,
|
||||||
"victimOf": relationship(
|
"victimOf": relationship(
|
||||||
ProjectedFit,
|
ProjectedFit,
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
# ===============================================================================
|
|
||||||
# Copyright (C) 2010 Diego Duclos
|
|
||||||
#
|
|
||||||
# This file is part of pyfa.
|
|
||||||
#
|
|
||||||
# pyfa is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# pyfa is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
# ===============================================================================
|
|
||||||
|
|
||||||
import eos.db
|
|
||||||
from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern
|
|
||||||
from eos.saveddata.targetProfile import TargetProfile as es_TargetProfile
|
|
||||||
|
|
||||||
|
|
||||||
class ImportError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultDatabaseValues:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
instance = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def importDamageProfileDefaults(cls):
|
|
||||||
damageProfileList = [["Uniform", "25", "25", "25", "25"], ["[Generic]EM", "100", "0", "0", "0"],
|
|
||||||
["[Generic]Thermal", "0", "100", "0", "0"], ["[Generic]Kinetic", "0", "0", "100", "0"],
|
|
||||||
["[Generic]Explosive", "0", "0", "0", "100"],
|
|
||||||
["[NPC][Asteroid] Blood Raiders", "5067", "4214", "0", "0"],
|
|
||||||
["[Bombs]Electron Bomb", "6400", "0", "0", "0"],
|
|
||||||
["[Bombs]Scorch Bomb", "0", "6400", "0", "0"],
|
|
||||||
["[Bombs]Concussion Bomb", "0", "0", "6400", "0"],
|
|
||||||
["[Bombs]Shrapnel Bomb", "0", "0", "0", "6400"],
|
|
||||||
["[Frequency Crystals][T2] Conflagration", "61.6", "61.6", "0", "0"],
|
|
||||||
["[Frequency Crystals][T2] Scorch", "72", "16", "0", "0"],
|
|
||||||
["[Frequency Crystals][T2] Gleam", "56", "56", "0", "0"],
|
|
||||||
["[Frequency Crystals][T2] Aurora", "40", "24", "0", "0"],
|
|
||||||
["[Frequency Crystals]Multifrequency", "61.6", "44", "0", "0"],
|
|
||||||
["[Frequency Crystals]Gamma", "61.6", "35.2", "0", "0"],
|
|
||||||
["[Frequency Crystals]Xray", "52.8", "35.2", "0", "0"],
|
|
||||||
["[Frequency Crystals]Ultraviolet", "52.8", "26.4", "0", "0"],
|
|
||||||
["[Frequency Crystals]Standard", "44", "26.4", "0", "0"],
|
|
||||||
["[Frequency Crystals]Infrared", "44", "17.6", "0", "0"],
|
|
||||||
["[Frequency Crystals]Microwave", "35.2", "17.6", "0", "0"],
|
|
||||||
["[Frequency Crystals]Radio", "44", "0", "0", "0"],
|
|
||||||
["[Hybrid Charges][T2] Void", "0", "61.6", "61.6", "0"],
|
|
||||||
["[Hybrid Charges][T2] Null", "0", "48", "40", "0"],
|
|
||||||
["[Hybrid Charges][T2] Javelin", "0", "64", "48", "0"],
|
|
||||||
["[Hybrid Charges][T2] Spike", "0", "32", "32", "0"],
|
|
||||||
["[Hybrid Charges]Antimatter", "0", "48", "67.2", "0"],
|
|
||||||
["[Hybrid Charges]Plutonium", "0", "48", "57.6", "0"],
|
|
||||||
["[Hybrid Charges]Uranium", "0", "38.4", "57.6", "0"],
|
|
||||||
["[Hybrid Charges]Thorium", "0", "38.4", "48", "0"],
|
|
||||||
["[Hybrid Charges]Lead", "0", "28.8", "48", "0"],
|
|
||||||
["[Hybrid Charges]Iridium", "0", "28.8", "38.4", "0"],
|
|
||||||
["[Hybrid Charges]Tungsten", "0", "19.2", "38.4", "0"],
|
|
||||||
["[Hybrid Charges]Iron", "0", "19.2", "28.8", "0"],
|
|
||||||
["[Missiles]Mjolnir", "100", "0", "0", "0"],
|
|
||||||
["[Missiles]Inferno", "0", "100", "0", "0"],
|
|
||||||
["[Missiles]Scourge", "0", "0", "100", "0"],
|
|
||||||
["[Missiles]Nova", "0", "0", "0", "100"],
|
|
||||||
["[Missiles][Structure] Standup Missile", "100", "100", "100", "100"],
|
|
||||||
["[Projectile Ammo][T2] Hail", "0", "0", "26.4", "96.8"],
|
|
||||||
["[Projectile Ammo][T2] Barrage", "0", "0", "40", "48"],
|
|
||||||
["[Projectile Ammo][T2] Quake", "0", "0", "40", "72"],
|
|
||||||
["[Projectile Ammo][T2] Tremor", "0", "0", "24", "40"],
|
|
||||||
["[Projectile Ammo]EMP", "79.2", "0", "8.8", "17.6"],
|
|
||||||
["[Projectile Ammo]Phased Plasma", "0", "88", "17.6", "0"],
|
|
||||||
["[Projectile Ammo]Fusion", "0", "0", "17.6", "88"],
|
|
||||||
["[Projectile Ammo]Depleted Uranium", "0", "26.4", "17.6", "26.4"],
|
|
||||||
["[Projectile Ammo]Titanium Sabot", "0", "0", "52.8", "176"],
|
|
||||||
["[Projectile Ammo]Proton", "26.4", "0", "17.6", "0"],
|
|
||||||
["[Projectile Ammo]Carbonized Lead", "0", "0", "35.2", "8.8"],
|
|
||||||
["[Projectile Ammo]Nuclear", "0", "0", "8.8", "35.2"],
|
|
||||||
# Different sizes of plasma do different damage, the values here are
|
|
||||||
# average of proportions across sizes
|
|
||||||
["[Exotic Plasma][T2] Occult", "0", "55863", "0", "44137"],
|
|
||||||
["[Exotic Plasma][T2] Mystic", "0", "66319", "0", "33681"],
|
|
||||||
["[Exotic Plasma]Tetryon", "0", "69208", "0", "30792"],
|
|
||||||
["[Exotic Plasma]Baryon", "0", "59737", "0", "40263"],
|
|
||||||
["[Exotic Plasma]Meson", "0", "60519", "0", "39481"],
|
|
||||||
["[NPC][Burner] Cruor (Blood Raiders)", "90", "90", "0", "0"],
|
|
||||||
["[NPC][Burner] Dramiel (Angel)", "55", "0", "20", "96"],
|
|
||||||
["[NPC][Burner] Daredevil (Serpentis)", "0", "110", "154", "0"],
|
|
||||||
["[NPC][Burner] Succubus (Sanshas Nation)", "135", "30", "0", "0"],
|
|
||||||
["[NPC][Burner] Worm (Guristas)", "0", "0", "228", "0"],
|
|
||||||
["[NPC][Burner] Enyo", "0", "147", "147", "0"],
|
|
||||||
["[NPC][Burner] Hawk", "0", "0", "247", "0"],
|
|
||||||
["[NPC][Burner] Jaguar", "36", "0", "50", "182"],
|
|
||||||
["[NPC][Burner] Vengeance", "232", "0", "0", "0"],
|
|
||||||
["[NPC][Burner] Ashimmu (Blood Raiders)", "260", "100", "0", "0"],
|
|
||||||
["[NPC][Burner] Talos", "0", "413", "413", "0"],
|
|
||||||
["[NPC][Burner] Sentinel", "0", "75", "0", "90"],
|
|
||||||
["[NPC][Asteroid] Angel Cartel", "1838", "562", "2215", "3838"],
|
|
||||||
["[NPC][Deadspace] Angel Cartel", "369", "533", "1395", "3302"],
|
|
||||||
["[NPC][Deadspace] Blood Raiders", "6040", "5052", "10", "15"],
|
|
||||||
["[NPC][Asteroid] Guristas", "0", "1828", "7413", "0"],
|
|
||||||
["[NPC][Deadspace] Guristas", "0", "1531", "9680", "0"],
|
|
||||||
["[NPC][Asteroid] Rogue Drone", "394", "666", "1090", "1687"],
|
|
||||||
["[NPC][Deadspace] Rogue Drone", "276", "1071", "1069", "871"],
|
|
||||||
["[NPC][Asteroid] Sanshas Nation", "5586", "4112", "0", "0"],
|
|
||||||
["[NPC][Deadspace] Sanshas Nation", "3009", "2237", "0", "0"],
|
|
||||||
["[NPC][Asteroid] Serpentis", "0", "5373", "4813", "0"],
|
|
||||||
["[NPC][Deadspace] Serpentis", "0", "3110", "1929", "0"],
|
|
||||||
["[NPC][Mission] Amarr Empire", "4464", "3546", "97", "0"],
|
|
||||||
["[NPC][Mission] Caldari State", "0", "2139", "4867", "0"],
|
|
||||||
["[NPC][Mission] CONCORD", "336", "134", "212", "412"],
|
|
||||||
["[NPC][Mission] Gallente Federation", "9", "3712", "2758", "0"],
|
|
||||||
["[NPC][Mission] Khanid", "612", "483", "43", "6"],
|
|
||||||
["[NPC][Mission] Minmatar Republic", "1024", "388", "1655", "4285"],
|
|
||||||
["[NPC][Mission] Mordus Legion", "25", "262", "625", "0"],
|
|
||||||
["[NPC][Mission] Thukker", "0", "52", "10", "79"],
|
|
||||||
["[NPC][Other] Sleepers", "1472", "1472", "1384", "1384"],
|
|
||||||
["[NPC][Other] Sansha Incursion", "1682", "1347", "3678", "3678"]]
|
|
||||||
|
|
||||||
for damageProfileRow in damageProfileList:
|
|
||||||
name, em, therm, kin, exp = damageProfileRow
|
|
||||||
damageProfile = eos.db.getDamagePattern(name)
|
|
||||||
if damageProfile is None:
|
|
||||||
damageProfile = es_DamagePattern(em, therm, kin, exp)
|
|
||||||
damageProfile.name = name
|
|
||||||
eos.db.save(damageProfile)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def importTargetProfileDefaults(cls):
|
|
||||||
targetProfileList = [["Uniform (25%)", "0.25", "0.25", "0.25", "0.25"],
|
|
||||||
["Uniform (50%)", "0.50", "0.50", "0.50", "0.50"],
|
|
||||||
["Uniform (75%)", "0.75", "0.75", "0.75", "0.75"],
|
|
||||||
["Uniform (90%)", "0.90", "0.90", "0.90", "0.90"],
|
|
||||||
["[T1 Resist]Shield", "0.0", "0.20", "0.40", "0.50"],
|
|
||||||
["[T1 Resist]Armor", "0.50", "0.45", "0.25", "0.10"],
|
|
||||||
["[T1 Resist]Hull", "0.33", "0.33", "0.33", "0.33"],
|
|
||||||
["[T1 Resist]Shield (+T2 DCU)", "0.125", "0.30", "0.475", "0.562"],
|
|
||||||
["[T1 Resist]Armor (+T2 DCU)", "0.575", "0.532", "0.363", "0.235"],
|
|
||||||
["[T1 Resist]Hull (+T2 DCU)", "0.598", "0.598", "0.598", "0.598"],
|
|
||||||
["[T2 Resist]Amarr (Shield)", "0.0", "0.20", "0.70", "0.875"],
|
|
||||||
["[T2 Resist]Amarr (Armor)", "0.50", "0.35", "0.625", "0.80"],
|
|
||||||
["[T2 Resist]Caldari (Shield)", "0.20", "0.84", "0.76", "0.60"],
|
|
||||||
["[T2 Resist]Caldari (Armor)", "0.50", "0.8625", "0.625", "0.10"],
|
|
||||||
["[T2 Resist]Gallente (Shield)", "0.0", "0.60", "0.85", "0.50"],
|
|
||||||
["[T2 Resist]Gallente (Armor)", "0.50", "0.675", "0.8375", "0.10"],
|
|
||||||
["[T2 Resist]Minmatar (Shield)", "0.75", "0.60", "0.40", "0.50"],
|
|
||||||
["[T2 Resist]Minmatar (Armor)", "0.90", "0.675", "0.25", "0.10"],
|
|
||||||
["[NPC][Asteroid] Angel Cartel", "0.54", "0.42", "0.37", "0.32"],
|
|
||||||
["[NPC][Asteroid] Blood Raiders", "0.34", "0.39", "0.45", "0.52"],
|
|
||||||
["[NPC][Asteroid] Guristas", "0.55", "0.35", "0.3", "0.48"],
|
|
||||||
["[NPC][Asteroid] Rogue Drones", "0.35", "0.38", "0.44", "0.49"],
|
|
||||||
["[NPC][Asteroid] Sanshas Nation", "0.35", "0.4", "0.47", "0.53"],
|
|
||||||
["[NPC][Asteroid] Serpentis", "0.49", "0.38", "0.29", "0.51"],
|
|
||||||
["[NPC][Deadspace] Angel Cartel", "0.59", "0.48", "0.4", "0.32"],
|
|
||||||
["[NPC][Deadspace] Blood Raiders", "0.31", "0.39", "0.47", "0.56"],
|
|
||||||
["[NPC][Deadspace] Guristas", "0.57", "0.39", "0.31", "0.5"],
|
|
||||||
["[NPC][Deadspace] Rogue Drones", "0.42", "0.42", "0.47", "0.49"],
|
|
||||||
["[NPC][Deadspace] Sanshas Nation", "0.31", "0.39", "0.47", "0.56"],
|
|
||||||
["[NPC][Deadspace] Serpentis", "0.49", "0.38", "0.29", "0.56"],
|
|
||||||
["[NPC][Mission] Amarr Empire", "0.34", "0.38", "0.42", "0.46"],
|
|
||||||
["[NPC][Mission] Caldari State", "0.51", "0.38", "0.3", "0.51"],
|
|
||||||
["[NPC][Mission] CONCORD", "0.47", "0.46", "0.47", "0.47"],
|
|
||||||
["[NPC][Mission] Gallente Federation", "0.51", "0.38", "0.31", "0.52"],
|
|
||||||
["[NPC][Mission] Khanid", "0.51", "0.42", "0.36", "0.4"],
|
|
||||||
["[NPC][Mission] Minmatar Republic", "0.51", "0.46", "0.41", "0.35"],
|
|
||||||
["[NPC][Mission] Mordus Legion", "0.32", "0.48", "0.4", "0.62"],
|
|
||||||
["[NPC][Other] Sleeper", "0.61", "0.61", "0.61", "0.61"],
|
|
||||||
["[NPC][Other] Sansha Incursion", "0.65", "0.63", "0.64", "0.65"],
|
|
||||||
["[NPC][Burner] Cruor (Blood Raiders)", "0.8", "0.73", "0.69", "0.67"],
|
|
||||||
["[NPC][Burner] Dramiel (Angel)", "0.35", "0.48", "0.61", "0.68"],
|
|
||||||
["[NPC][Burner] Daredevil (Serpentis)", "0.69", "0.59", "0.59", "0.43"],
|
|
||||||
["[NPC][Burner] Succubus (Sanshas Nation)", "0.35", "0.48", "0.61", "0.68"],
|
|
||||||
["[NPC][Burner] Worm (Guristas)", "0.48", "0.58", "0.69", "0.74"],
|
|
||||||
["[NPC][Burner] Enyo", "0.58", "0.72", "0.86", "0.24"],
|
|
||||||
["[NPC][Burner] Hawk", "0.3", "0.86", "0.79", "0.65"],
|
|
||||||
["[NPC][Burner] Jaguar", "0.78", "0.65", "0.48", "0.56"],
|
|
||||||
["[NPC][Burner] Vengeance", "0.66", "0.56", "0.75", "0.86"],
|
|
||||||
["[NPC][Burner] Ashimmu (Blood Raiders)", "0.8", "0.76", "0.68", "0.7"],
|
|
||||||
["[NPC][Burner] Talos", "0.68", "0.59", "0.59", "0.43"],
|
|
||||||
["[NPC][Burner] Sentinel", "0.58", "0.45", "0.52", "0.66"]]
|
|
||||||
|
|
||||||
for targetProfileRow in targetProfileList:
|
|
||||||
name = targetProfileRow[0]
|
|
||||||
em = targetProfileRow[1]
|
|
||||||
therm = targetProfileRow[2]
|
|
||||||
kin = targetProfileRow[3]
|
|
||||||
exp = targetProfileRow[4]
|
|
||||||
try:
|
|
||||||
maxVel = targetProfileRow[5]
|
|
||||||
except IndexError:
|
|
||||||
maxVel = None
|
|
||||||
try:
|
|
||||||
sigRad = targetProfileRow[6]
|
|
||||||
except IndexError:
|
|
||||||
sigRad = None
|
|
||||||
try:
|
|
||||||
radius = targetProfileRow[7]
|
|
||||||
except:
|
|
||||||
radius = None
|
|
||||||
targetProfile = eos.db.eos.db.getTargetProfile(name)
|
|
||||||
if targetProfile is None:
|
|
||||||
targetProfile = es_TargetProfile(em, therm, kin, exp, maxVel, sigRad, radius)
|
|
||||||
targetProfile.name = name
|
|
||||||
eos.db.save(targetProfile)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def importRequiredDefaults(cls):
|
|
||||||
damageProfileList = [["Uniform", "25", "25", "25", "25"]]
|
|
||||||
|
|
||||||
for damageProfileRow in damageProfileList:
|
|
||||||
name, em, therm, kin, exp = damageProfileRow
|
|
||||||
damageProfile = eos.db.getDamagePattern(name)
|
|
||||||
if damageProfile is None:
|
|
||||||
damageProfile = es_DamagePattern(em, therm, kin, exp)
|
|
||||||
damageProfile.name = name
|
|
||||||
eos.db.save(damageProfile)
|
|
||||||
@@ -42,6 +42,7 @@ modules_table = Table("modules", saveddata_meta,
|
|||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||||
Column("spoolType", Integer, nullable=True),
|
Column("spoolType", Integer, nullable=True),
|
||||||
Column("spoolAmount", Float, nullable=True),
|
Column("spoolAmount", Float, nullable=True),
|
||||||
|
Column("projectionRange", Float, nullable=True),
|
||||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||||
|
|
||||||
mapper(Module, modules_table,
|
mapper(Module, modules_table,
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ def getDamagePattern(lookfor, eager=None):
|
|||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
pattern = saveddata_session.query(DamagePattern).options(*eager).filter(
|
pattern = saveddata_session.query(DamagePattern).options(*eager).filter(
|
||||||
DamagePattern.name == lookfor).first()
|
DamagePattern.rawName == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return pattern
|
return pattern
|
||||||
@@ -434,7 +434,7 @@ def getTargetProfile(lookfor, eager=None):
|
|||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
pattern = saveddata_session.query(TargetProfile).options(*eager).filter(
|
pattern = saveddata_session.query(TargetProfile).options(*eager).filter(
|
||||||
TargetProfile.name == lookfor).first()
|
TargetProfile.rawName == lookfor).first()
|
||||||
else:
|
else:
|
||||||
raise TypeError("Need integer or string as argument")
|
raise TypeError("Need integer or string as argument")
|
||||||
return pattern
|
return pattern
|
||||||
@@ -470,7 +470,7 @@ def searchFits(nameLike, where=None, eager=None):
|
|||||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||||
eager = processEager(eager)
|
eager = processEager(eager)
|
||||||
with sd_lock:
|
with sd_lock:
|
||||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).limit(100).all())
|
||||||
|
|
||||||
return fits
|
return fits
|
||||||
|
|
||||||
@@ -560,6 +560,8 @@ def commit():
|
|||||||
with sd_lock:
|
with sd_lock:
|
||||||
try:
|
try:
|
||||||
saveddata_session.commit()
|
saveddata_session.commit()
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
saveddata_session.rollback()
|
saveddata_session.rollback()
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
@@ -570,6 +572,8 @@ def flush():
|
|||||||
with sd_lock:
|
with sd_lock:
|
||||||
try:
|
try:
|
||||||
saveddata_session.flush()
|
saveddata_session.flush()
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
saveddata_session.rollback()
|
saveddata_session.rollback()
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
|
|||||||
@@ -24,23 +24,28 @@ import datetime
|
|||||||
from eos.db import saveddata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.saveddata.targetProfile import TargetProfile
|
from eos.saveddata.targetProfile import TargetProfile
|
||||||
|
|
||||||
targetProfiles_table = Table("targetResists", saveddata_meta,
|
|
||||||
Column("ID", Integer, primary_key=True),
|
|
||||||
Column("name", String),
|
|
||||||
Column("emAmount", Float),
|
|
||||||
Column("thermalAmount", Float),
|
|
||||||
Column("kineticAmount", Float),
|
|
||||||
Column("explosiveAmount", Float),
|
|
||||||
Column("maxVelocity", Float, nullable=True),
|
|
||||||
Column("signatureRadius", Float, nullable=True),
|
|
||||||
Column("radius", Float, nullable=True),
|
|
||||||
Column("ownerID", ForeignKey("users.ID"), nullable=True),
|
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
|
||||||
)
|
|
||||||
|
|
||||||
mapper(TargetProfile, targetProfiles_table,
|
targetProfiles_table = Table(
|
||||||
properties={
|
'targetResists',
|
||||||
"_maxVelocity": targetProfiles_table.c.maxVelocity,
|
saveddata_meta,
|
||||||
"_signatureRadius": targetProfiles_table.c.signatureRadius,
|
Column('ID', Integer, primary_key=True),
|
||||||
"_radius": targetProfiles_table.c.radius})
|
Column('name', String),
|
||||||
|
Column('emAmount', Float),
|
||||||
|
Column('thermalAmount', Float),
|
||||||
|
Column('kineticAmount', Float),
|
||||||
|
Column('explosiveAmount', Float),
|
||||||
|
Column('maxVelocity', Float, nullable=True),
|
||||||
|
Column('signatureRadius', Float, nullable=True),
|
||||||
|
Column('radius', Float, nullable=True),
|
||||||
|
Column('ownerID', ForeignKey('users.ID'), nullable=True),
|
||||||
|
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
|
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
|
||||||
|
|
||||||
|
mapper(
|
||||||
|
TargetProfile,
|
||||||
|
targetProfiles_table,
|
||||||
|
properties={
|
||||||
|
'rawName': targetProfiles_table.c.name,
|
||||||
|
'_maxVelocity': targetProfiles_table.c.maxVelocity,
|
||||||
|
'_signatureRadius': targetProfiles_table.c.signatureRadius,
|
||||||
|
'_radius': targetProfiles_table.c.radius})
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
from sqlalchemy.orm.collections import collection
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
@@ -138,9 +139,10 @@ class HandledModuleList(HandledList):
|
|||||||
else:
|
else:
|
||||||
self.appendIgnoreEmpty(mod)
|
self.appendIgnoreEmpty(mod)
|
||||||
|
|
||||||
|
@collection.appender
|
||||||
def appendIgnoreEmpty(self, mod):
|
def appendIgnoreEmpty(self, mod):
|
||||||
mod.position = len(self)
|
mod.position = len(self)
|
||||||
HandledList.append(self, mod)
|
super().append(mod)
|
||||||
if mod.isInvalid:
|
if mod.isInvalid:
|
||||||
self.remove(mod)
|
self.remove(mod)
|
||||||
|
|
||||||
|
|||||||
5964
eos/effects.py
5964
eos/effects.py
File diff suppressed because it is too large
Load Diff
109
eos/gamedata.py
109
eos/gamedata.py
@@ -18,6 +18,7 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
@@ -183,6 +184,8 @@ class Effect(EqBase):
|
|||||||
self.__activeByDefault = True
|
self.__activeByDefault = True
|
||||||
self.__type = None
|
self.__type = None
|
||||||
pyfalog.error("AttributeError generating handler: {0}", e)
|
pyfalog.error("AttributeError generating handler: {0}", e)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.__handler = eos.effects.DummyEffect.handler
|
self.__handler = eos.effects.DummyEffect.handler
|
||||||
self.__runTime = "normal"
|
self.__runTime = "normal"
|
||||||
@@ -199,45 +202,20 @@ class Effect(EqBase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return self.__effectDef.get(key, None)
|
return self.__effectDef.get(key, None)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
return getattr(self.__effectDef, key, None)
|
return getattr(self.__effectDef, key, None)
|
||||||
|
|
||||||
|
|
||||||
class Item(EqBase):
|
class Item(EqBase):
|
||||||
MOVE_ATTRS = (4, # Mass
|
|
||||||
38, # Capacity
|
|
||||||
161) # Volume
|
|
||||||
|
|
||||||
MOVE_ATTR_INFO = None
|
|
||||||
|
|
||||||
ABYSSAL_TYPES = None
|
ABYSSAL_TYPES = None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def getMoveAttrInfo(cls):
|
|
||||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
|
||||||
if info is None:
|
|
||||||
cls.MOVE_ATTR_INFO = info = []
|
|
||||||
for id in cls.MOVE_ATTRS:
|
|
||||||
info.append(eos.db.getAttributeInfo(id))
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def moveAttrs(self):
|
|
||||||
self.__moved = True
|
|
||||||
for info in self.getMoveAttrInfo():
|
|
||||||
val = getattr(self, info.name, 0)
|
|
||||||
if val != 0:
|
|
||||||
attr = Attribute()
|
|
||||||
attr.info = info
|
|
||||||
attr.value = val
|
|
||||||
self.__attributes[info.name] = attr
|
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
def init(self):
|
def init(self):
|
||||||
self.__race = None
|
self.__race = None
|
||||||
self.__requiredSkills = None
|
self.__requiredSkills = None
|
||||||
self.__requiredFor = None
|
self.__requiredFor = None
|
||||||
self.__moved = False
|
|
||||||
self.__offensive = None
|
self.__offensive = None
|
||||||
self.__assistive = None
|
self.__assistive = None
|
||||||
self.__overrides = None
|
self.__overrides = None
|
||||||
@@ -259,9 +237,6 @@ class Item(EqBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def attributes(self):
|
def attributes(self):
|
||||||
if not self.__moved:
|
|
||||||
self.moveAttrs()
|
|
||||||
|
|
||||||
return self.__attributes
|
return self.__attributes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -314,50 +289,26 @@ class Item(EqBase):
|
|||||||
eos.db.saveddata_session.delete(override)
|
eos.db.saveddata_session.delete(override)
|
||||||
eos.db.commit()
|
eos.db.commit()
|
||||||
|
|
||||||
srqIDMap = {182: 277, 183: 278, 184: 279, 1285: 1286, 1289: 1287, 1290: 1288}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requiredSkills(self):
|
def requiredSkills(self):
|
||||||
if self.__requiredSkills is None:
|
if self.__requiredSkills is None:
|
||||||
requiredSkills = OrderedDict()
|
self.__requiredSkills = {}
|
||||||
self.__requiredSkills = requiredSkills
|
if self.reqskills:
|
||||||
# Map containing attribute IDs we may need for required skills
|
for skillTypeID, skillLevel in json.loads(self.reqskills).items():
|
||||||
# { requiredSkillX : requiredSkillXLevel }
|
skillItem = eos.db.getItem(int(skillTypeID))
|
||||||
combinedAttrIDs = set(self.srqIDMap.keys()).union(set(self.srqIDMap.values()))
|
if skillItem:
|
||||||
# Map containing result of the request
|
self.__requiredSkills[skillItem] = skillLevel
|
||||||
# { attributeID : attributeValue }
|
|
||||||
skillAttrs = {}
|
|
||||||
# Get relevant attribute values from db (required skill IDs and levels) for our item
|
|
||||||
for attrInfo in eos.db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)):
|
|
||||||
attrID = attrInfo[1]
|
|
||||||
attrVal = attrInfo[2]
|
|
||||||
skillAttrs[attrID] = attrVal
|
|
||||||
# Go through all attributeID pairs
|
|
||||||
for srqIDAtrr, srqLvlAttr in self.srqIDMap.items():
|
|
||||||
# Check if we have both in returned result
|
|
||||||
if srqIDAtrr in skillAttrs and srqLvlAttr in skillAttrs:
|
|
||||||
skillID = int(skillAttrs[srqIDAtrr])
|
|
||||||
skillLvl = skillAttrs[srqLvlAttr]
|
|
||||||
# Fetch item from database and fill map
|
|
||||||
item = eos.db.getItem(skillID)
|
|
||||||
requiredSkills[item] = skillLvl
|
|
||||||
return self.__requiredSkills
|
return self.__requiredSkills
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requiredFor(self):
|
def requiredFor(self):
|
||||||
if self.__requiredFor is None:
|
if self.__requiredFor is None:
|
||||||
self.__requiredFor = dict()
|
self.__requiredFor = {}
|
||||||
|
if self.requiredfor:
|
||||||
# Map containing attribute IDs we may need for required skills
|
for typeID, skillLevel in json.loads(self.requiredfor).items():
|
||||||
|
requiredForItem = eos.db.getItem(int(typeID))
|
||||||
# Get relevant attribute values from db (required skill IDs and levels) for our item
|
if requiredForItem:
|
||||||
q = eos.db.getRequiredFor(self.ID, self.srqIDMap)
|
self.__requiredFor[requiredForItem] = skillLevel
|
||||||
|
|
||||||
for itemID, lvl in q:
|
|
||||||
# Fetch item from database and fill map
|
|
||||||
item = eos.db.getItem(itemID)
|
|
||||||
self.__requiredFor[item] = lvl
|
|
||||||
|
|
||||||
return self.__requiredFor
|
return self.__requiredFor
|
||||||
|
|
||||||
factionMap = {
|
factionMap = {
|
||||||
@@ -373,7 +324,8 @@ class Item(EqBase):
|
|||||||
500016: "sisters",
|
500016: "sisters",
|
||||||
500018: "mordu",
|
500018: "mordu",
|
||||||
500019: "sansha",
|
500019: "sansha",
|
||||||
500020: "serpentis"
|
500020: "serpentis",
|
||||||
|
500026: "triglavian"
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -399,6 +351,7 @@ class Item(EqBase):
|
|||||||
9 : "guristas", # Caldari + Gallente
|
9 : "guristas", # Caldari + Gallente
|
||||||
10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills
|
10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills
|
||||||
12 : "sisters", # Amarr + Gallente
|
12 : "sisters", # Amarr + Gallente
|
||||||
|
15 : "concord",
|
||||||
16 : "jove",
|
16 : "jove",
|
||||||
32 : "sansha", # Incrusion Sansha
|
32 : "sansha", # Incrusion Sansha
|
||||||
128: "ore",
|
128: "ore",
|
||||||
@@ -530,6 +483,14 @@ class Item(EqBase):
|
|||||||
def isBooster(self):
|
def isBooster(self):
|
||||||
return self.group.name == 'Booster' and self.category.name == 'Implant'
|
return self.group.name == 'Booster' and self.category.name == 'Implant'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def isStandup(self):
|
||||||
|
if self.category.name == "Structure Module":
|
||||||
|
return True
|
||||||
|
if self.isFighter and {'fighterSquadronIsStandupLight', 'fighterSquadronIsStandupHeavy', 'fighterSquadronIsStandupSupport'}.intersection(self.attributes):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Item(ID={}, name={}) at {}".format(
|
return "Item(ID={}, name={}) at {}".format(
|
||||||
self.ID, self.name, hex(id(self))
|
self.ID, self.name, hex(id(self))
|
||||||
@@ -601,10 +562,6 @@ class MetaGroup(EqBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MetaType(EqBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Unit(EqBase):
|
class Unit(EqBase):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -736,5 +693,15 @@ class Unit(EqBase):
|
|||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
class Traits(EqBase):
|
class Traits(EqBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ImplantSet(EqBase):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullName(self):
|
||||||
|
if not self.gradeName:
|
||||||
|
return self.setName
|
||||||
|
return '{} {}'.format(self.gradeName, self.setName)
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
import collections
|
|
||||||
|
from collections.abc import MutableMapping
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from math import exp
|
from math import exp
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ from eos.db.gamedata.queries import getAttributeInfo
|
|||||||
|
|
||||||
defaultValuesCache = {}
|
defaultValuesCache = {}
|
||||||
cappingAttrKeyCache = {}
|
cappingAttrKeyCache = {}
|
||||||
|
resistanceCache = {}
|
||||||
|
|
||||||
|
|
||||||
def getAttrDefault(key, fallback=None):
|
def getAttrDefault(key, fallback=None):
|
||||||
@@ -46,19 +48,23 @@ def getAttrDefault(key, fallback=None):
|
|||||||
|
|
||||||
|
|
||||||
def getResistanceAttrID(modifyingItem, effect):
|
def getResistanceAttrID(modifyingItem, effect):
|
||||||
# If it doesn't exist on the effect, check the modifying modules attributes. If it's there, set it on the
|
# If it doesn't exist on the effect, check the modifying module's attributes.
|
||||||
# effect for this session so that we don't have to look here again (won't always work when it's None, but
|
# If it's there, cache it and return
|
||||||
# will catch most)
|
if effect.resistanceID:
|
||||||
if not effect.getattr('resistanceCalculated'):
|
return effect.resistanceID
|
||||||
|
cacheKey = (modifyingItem.item.ID, effect.ID)
|
||||||
|
try:
|
||||||
|
return resistanceCache[cacheKey]
|
||||||
|
except KeyError:
|
||||||
attrPrefix = effect.getattr('prefix')
|
attrPrefix = effect.getattr('prefix')
|
||||||
if attrPrefix:
|
if attrPrefix:
|
||||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None
|
resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None
|
||||||
if not effect.resistanceID:
|
if not resistanceID:
|
||||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None
|
resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None
|
||||||
else:
|
else:
|
||||||
effect.resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None
|
resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None
|
||||||
effect.resistanceCalculated = True
|
resistanceCache[cacheKey] = resistanceID
|
||||||
return effect.resistanceID
|
return resistanceID
|
||||||
|
|
||||||
|
|
||||||
class ItemAttrShortcut:
|
class ItemAttrShortcut:
|
||||||
@@ -67,14 +73,8 @@ class ItemAttrShortcut:
|
|||||||
return_value = self.itemModifiedAttributes.get(key)
|
return_value = self.itemModifiedAttributes.get(key)
|
||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
def getModifiedItemAttrWithExtraMods(self, key, extraMultipliers=None, default=0):
|
def getModifiedItemAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
|
||||||
"""Returns attribute value with passed modifiers applied to it."""
|
return_value = self.itemModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
|
||||||
return_value = self.itemModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
|
|
||||||
return return_value or default
|
|
||||||
|
|
||||||
def getModifiedItemAttrWithoutAfflictor(self, key, afflictor, default=0):
|
|
||||||
"""Returns attribute value with passed afflictor modification removed."""
|
|
||||||
return_value = self.itemModifiedAttributes.getWithoutAfflictor(key, afflictor)
|
|
||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
def getItemBaseAttrValue(self, key, default=0):
|
def getItemBaseAttrValue(self, key, default=0):
|
||||||
@@ -88,14 +88,8 @@ class ChargeAttrShortcut:
|
|||||||
return_value = self.chargeModifiedAttributes.get(key)
|
return_value = self.chargeModifiedAttributes.get(key)
|
||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
def getModifiedChargeAttrWithExtraMods(self, key, extraMultipliers=None, default=0):
|
def getModifiedChargeAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
|
||||||
"""Returns attribute value with passed modifiers applied to it."""
|
return_value = self.chargeModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
|
||||||
return_value = self.chargeModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
|
|
||||||
return return_value or default
|
|
||||||
|
|
||||||
def getModifiedChargeAttrWithoutAfflictor(self, key, afflictor, default=0):
|
|
||||||
"""Returns attribute value with passed modifiers applied to it."""
|
|
||||||
return_value = self.chargeModifiedAttributes.getWithoutAfflictor(key, afflictor)
|
|
||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
def getChargeBaseAttrValue(self, key, default=0):
|
def getChargeBaseAttrValue(self, key, default=0):
|
||||||
@@ -103,7 +97,7 @@ class ChargeAttrShortcut:
|
|||||||
return return_value or default
|
return return_value or default
|
||||||
|
|
||||||
|
|
||||||
class ModifiedAttributeDict(collections.MutableMapping):
|
class ModifiedAttributeDict(MutableMapping):
|
||||||
overrides_enabled = False
|
overrides_enabled = False
|
||||||
|
|
||||||
class CalculationPlaceholder:
|
class CalculationPlaceholder:
|
||||||
@@ -211,32 +205,11 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
# Original value is the least priority
|
# Original value is the least priority
|
||||||
return self.getOriginal(key)
|
return self.getOriginal(key)
|
||||||
|
|
||||||
def getWithExtraMods(self, key, extraMultipliers=None, default=0):
|
def getExtended(self, key, extraMultipliers=None, ignoreAfflictors=None, default=0):
|
||||||
"""Copy of __getitem__ with some modifications."""
|
"""
|
||||||
if not extraMultipliers:
|
Here we consider couple of parameters. If they affect final result, we do
|
||||||
return self.get(key, default=default)
|
not store result, and if they are - we do.
|
||||||
|
"""
|
||||||
val = self.__calculateValue(key, extraMultipliers=extraMultipliers)
|
|
||||||
if val is not None:
|
|
||||||
return val
|
|
||||||
|
|
||||||
# Then in values which are not yet calculated
|
|
||||||
if self.__intermediary:
|
|
||||||
val = self.__intermediary.get(key)
|
|
||||||
else:
|
|
||||||
val = None
|
|
||||||
if val is not None:
|
|
||||||
return val
|
|
||||||
|
|
||||||
# Original value
|
|
||||||
val = self.getOriginal(key)
|
|
||||||
if val is not None:
|
|
||||||
return val
|
|
||||||
|
|
||||||
# Passed in default value
|
|
||||||
return default
|
|
||||||
|
|
||||||
def getWithoutAfflictor(self, key, afflictor, default=0):
|
|
||||||
# Here we do not have support for preAssigns/forceds, as doing them would
|
# Here we do not have support for preAssigns/forceds, as doing them would
|
||||||
# mean that we have to store all of them in a list which increases memory use,
|
# mean that we have to store all of them in a list which increases memory use,
|
||||||
# and we do not actually need those operators atm
|
# and we do not actually need those operators atm
|
||||||
@@ -245,8 +218,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
ignorePenalizedMultipliers = {}
|
ignorePenalizedMultipliers = {}
|
||||||
postIncreaseAdjustment = 0
|
postIncreaseAdjustment = 0
|
||||||
for fit, afflictors in self.getAfflictions(key).items():
|
for fit, afflictors in self.getAfflictions(key).items():
|
||||||
for innerAfflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
|
||||||
if innerAfflictor is afflictor:
|
if afflictor in ignoreAfflictors:
|
||||||
if operator == Operator.MULTIPLY:
|
if operator == Operator.MULTIPLY:
|
||||||
if stackingGroup is None:
|
if stackingGroup is None:
|
||||||
multiplierAdjustment /= postResAmount
|
multiplierAdjustment /= postResAmount
|
||||||
@@ -257,29 +230,31 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
elif operator == Operator.POSTINCREASE:
|
elif operator == Operator.POSTINCREASE:
|
||||||
postIncreaseAdjustment -= postResAmount
|
postIncreaseAdjustment -= postResAmount
|
||||||
|
|
||||||
if preIncreaseAdjustment == 0 and multiplierAdjustment == 1 and postIncreaseAdjustment == 0 and len(ignorePenalizedMultipliers) == 0:
|
# If we apply no customizations - use regular getter
|
||||||
|
if (
|
||||||
|
not extraMultipliers and
|
||||||
|
preIncreaseAdjustment == 0 and multiplierAdjustment == 1 and
|
||||||
|
postIncreaseAdjustment == 0 and len(ignorePenalizedMultipliers) == 0
|
||||||
|
):
|
||||||
return self.get(key, default=default)
|
return self.get(key, default=default)
|
||||||
|
|
||||||
|
# Try to calculate custom values
|
||||||
val = self.__calculateValue(
|
val = self.__calculateValue(
|
||||||
key, preIncAdj=preIncreaseAdjustment, multAdj=multiplierAdjustment,
|
key, extraMultipliers=extraMultipliers, preIncAdj=preIncreaseAdjustment, multAdj=multiplierAdjustment,
|
||||||
postIncAdj=postIncreaseAdjustment, ignorePenMult=ignorePenalizedMultipliers)
|
postIncAdj=postIncreaseAdjustment, ignorePenMult=ignorePenalizedMultipliers)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
# Then in values which are not yet calculated
|
# Then the same fallbacks as in regular getter
|
||||||
if self.__intermediary:
|
if self.__intermediary:
|
||||||
val = self.__intermediary.get(key)
|
val = self.__intermediary.get(key)
|
||||||
else:
|
else:
|
||||||
val = None
|
val = None
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
# Original value
|
|
||||||
val = self.getOriginal(key)
|
val = self.getOriginal(key)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
return val
|
return val
|
||||||
|
|
||||||
# Passed in default value
|
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
@@ -584,10 +559,12 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
if 'projected' not in effectType:
|
if 'projected' not in effectType:
|
||||||
return 1
|
return 1
|
||||||
remoteResistID = getResistanceAttrID(modifyingItem=fit.getModifier(), effect=effect)
|
remoteResistID = getResistanceAttrID(modifyingItem=fit.getModifier(), effect=effect)
|
||||||
|
if not remoteResistID:
|
||||||
|
return 1
|
||||||
attrInfo = getAttributeInfo(remoteResistID)
|
attrInfo = getAttributeInfo(remoteResistID)
|
||||||
# Get the attribute of the resist
|
# Get the attribute of the resist
|
||||||
resist = fit.ship.itemModifiedAttributes[attrInfo.attributeName] or None
|
resist = fit.ship.itemModifiedAttributes[attrInfo.attributeName] or None
|
||||||
return resist or 1.0
|
return resist or 1
|
||||||
|
|
||||||
|
|
||||||
class Affliction:
|
class Affliction:
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class Booster(HandledItem, ItemAttrShortcut):
|
|||||||
(effect.isType("passive") or effect.isType("boosterSideEffect")):
|
(effect.isType("passive") or effect.isType("boosterSideEffect")):
|
||||||
if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects:
|
if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects:
|
||||||
continue
|
continue
|
||||||
effect.handler(fit, self, ("booster",))
|
effect.handler(fit, self, ("booster",), None, effect=effect)
|
||||||
|
|
||||||
@validates("ID", "itemID", "ammoID", "active")
|
@validates("ID", "itemID", "ammoID", "active")
|
||||||
def validator(self, key, val):
|
def validator(self, key, val):
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ from logbook import Logger
|
|||||||
|
|
||||||
from sqlalchemy.orm import reconstructor
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
|
from eos.utils.round import roundToPrec
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -56,9 +59,8 @@ class BoosterSideEffect:
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return "{0}% {1}".format(
|
return "{0}% {1}".format(
|
||||||
self.booster.getModifiedItemAttr(self.attr),
|
roundToPrec(self.booster.getModifiedItemAttr(self.attr), 5),
|
||||||
self.__effect.getattr('displayName') or self.__effect.name,
|
self.__effect.getattr('displayName') or self.__effect.name)
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attr(self):
|
def attr(self):
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ class Skill(HandledItem):
|
|||||||
(not fit.isStructure or effect.isType("structure")) and \
|
(not fit.isStructure or effect.isType("structure")) and \
|
||||||
effect.activeByDefault:
|
effect.activeByDefault:
|
||||||
try:
|
try:
|
||||||
effect.handler(fit, self, ("skill",))
|
effect.handler(fit, self, ("skill",), None, effect=effect)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -18,21 +18,174 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
|
|
||||||
|
|
||||||
|
# Order is significant here - UI uses order as-is for built-in patterns
|
||||||
|
BUILTINS = OrderedDict([
|
||||||
|
(-1, ('Uniform', 25, 25, 25, 25)),
|
||||||
|
(-2, ('[Generic]EM', 1, 0, 0, 0)),
|
||||||
|
(-3, ('[Generic]Thermal', 0, 1, 0, 0)),
|
||||||
|
(-4, ('[Generic]Kinetic', 0, 0, 1, 0)),
|
||||||
|
(-5, ('[Generic]Explosive', 0, 0, 0, 1)),
|
||||||
|
(-6, ('[Frequency Crystals]|[T2] Aurora', 5, 3, 0, 0)),
|
||||||
|
(-7, ('[Frequency Crystals]|[T2] Scorch', 9, 2, 0, 0)),
|
||||||
|
(-8, ('[Frequency Crystals]Radio', 5, 0, 0, 0)),
|
||||||
|
(-9, ('[Frequency Crystals]Microwave', 4, 2, 0, 0)),
|
||||||
|
(-10, ('[Frequency Crystals]Infrared', 5, 2, 0, 0)),
|
||||||
|
(-11, ('[Frequency Crystals]Standard', 5, 3, 0, 0)),
|
||||||
|
(-12, ('[Frequency Crystals]Ultraviolet', 6, 3, 0, 0)),
|
||||||
|
(-13, ('[Frequency Crystals]Xray', 6, 4, 0, 0)),
|
||||||
|
(-14, ('[Frequency Crystals]Gamma', 7, 4, 0, 0)),
|
||||||
|
(-15, ('[Frequency Crystals]Multifrequency', 7, 5, 0, 0)),
|
||||||
|
(-16, ('[Frequency Crystals]|[T2] Gleam', 7, 7, 0, 0)),
|
||||||
|
(-17, ('[Frequency Crystals]|[T2] Conflagration', 7.7, 7.7, 0, 0)),
|
||||||
|
# Different sizes of plasma do different damage ratios, the values here
|
||||||
|
# are average of ratios across sizes
|
||||||
|
(-18, ('[Exotic Plasma]|[T2] Mystic', 0, 66319, 0, 33681)),
|
||||||
|
(-19, ('[Exotic Plasma]Meson', 0, 60519, 0, 39481)),
|
||||||
|
(-20, ('[Exotic Plasma]Baryon', 0, 59737, 0, 40263)),
|
||||||
|
(-21, ('[Exotic Plasma]Tetryon', 0, 69208, 0, 30792)),
|
||||||
|
(-22, ('[Exotic Plasma]|[T2] Occult', 0, 55863, 0, 44137)),
|
||||||
|
(-23, ('[Hybrid Charges]|[T2] Spike', 0, 4, 4, 0)),
|
||||||
|
(-24, ('[Hybrid Charges]|[T2] Null', 0, 6, 5, 0)),
|
||||||
|
(-25, ('[Hybrid Charges]Iron', 0, 2, 3, 0)),
|
||||||
|
(-26, ('[Hybrid Charges]Tungsten', 0, 2, 4, 0)),
|
||||||
|
(-27, ('[Hybrid Charges]Iridium', 0, 3, 4, 0)),
|
||||||
|
(-28, ('[Hybrid Charges]Lead', 0, 3, 5, 0)),
|
||||||
|
(-29, ('[Hybrid Charges]Thorium', 0, 4, 5, 0)),
|
||||||
|
(-30, ('[Hybrid Charges]Uranium', 0, 4, 6, 0)),
|
||||||
|
(-31, ('[Hybrid Charges]Plutonium', 0, 5, 6, 0)),
|
||||||
|
(-32, ('[Hybrid Charges]Antimatter', 0, 5, 7, 0)),
|
||||||
|
(-33, ('[Hybrid Charges]|[T2] Javelin', 0, 8, 6, 0)),
|
||||||
|
(-34, ('[Hybrid Charges]|[T2] Void', 0, 7.7, 7.7, 0)),
|
||||||
|
(-35, ('[Projectile Ammo]|[T2] Tremor', 0, 0, 3, 5)),
|
||||||
|
(-36, ('[Projectile Ammo]|[T2] Barrage', 0, 0, 5, 6)),
|
||||||
|
(-37, ('[Projectile Ammo]Carbonized Lead', 0, 0, 4, 1)),
|
||||||
|
(-38, ('[Projectile Ammo]Nuclear', 0, 0, 1, 4)),
|
||||||
|
(-39, ('[Projectile Ammo]Proton', 3, 0, 2, 0)),
|
||||||
|
(-40, ('[Projectile Ammo]Depleted Uranium', 0, 3, 2, 3)),
|
||||||
|
(-41, ('[Projectile Ammo]Titanium Sabot', 0, 0, 6, 2)),
|
||||||
|
(-42, ('[Projectile Ammo]EMP', 9, 0, 1, 2)),
|
||||||
|
(-43, ('[Projectile Ammo]Phased Plasma', 0, 10, 2, 0)),
|
||||||
|
(-44, ('[Projectile Ammo]Fusion', 0, 0, 2, 10)),
|
||||||
|
(-45, ('[Projectile Ammo]|[T2] Quake', 0, 0, 5, 9)),
|
||||||
|
(-46, ('[Projectile Ammo]|[T2] Hail', 0, 0, 3.3, 12.1)),
|
||||||
|
(-47, ('[Missiles]Mjolnir', 1, 0, 0, 0)),
|
||||||
|
(-48, ('[Missiles]Inferno', 0, 1, 0, 0)),
|
||||||
|
(-49, ('[Missiles]Scourge', 0, 0, 1, 0)),
|
||||||
|
(-50, ('[Missiles]Nova', 0, 0, 0, 1)),
|
||||||
|
(-51, ('[Bombs]Electron Bomb', 6400, 0, 0, 0)),
|
||||||
|
(-52, ('[Bombs]Scorch Bomb', 0, 6400, 0, 0)),
|
||||||
|
(-53, ('[Bombs]Concussion Bomb', 0, 0, 6400, 0)),
|
||||||
|
(-54, ('[Bombs]Shrapnel Bomb', 0, 0, 0, 6400)),
|
||||||
|
# Source: ticket #2067
|
||||||
|
(-55, ('[NPC][Abyssal]All', 130, 396, 258, 216)),
|
||||||
|
(-56, ('[NPC][Abyssal]Drifter', 250, 250, 250, 250)),
|
||||||
|
(-57, ('[NPC][Abyssal]Drones', 250, 250, 250, 250)),
|
||||||
|
(-58, ('[NPC][Abyssal]Overmind', 0, 408, 592, 0)),
|
||||||
|
(-59, ('[NPC][Abyssal]Seeker', 406, 406, 94, 94)),
|
||||||
|
(-60, ('[NPC][Abyssal]Sleeper', 313, 313, 187, 187)),
|
||||||
|
(-61, ('[NPC][Abyssal]Triglavian', 0, 610, 0, 390)),
|
||||||
|
(-62, ('[NPC][Asteroid]Angel Cartel', 1838, 562, 2215, 3838)),
|
||||||
|
(-63, ('[NPC][Asteroid]Blood Raiders', 5067, 4214, 0, 0)),
|
||||||
|
(-64, ('[NPC][Asteroid]Guristas', 0, 1828, 7413, 0)),
|
||||||
|
(-65, ('[NPC][Asteroid]Rogue Drone', 394, 666, 1090, 1687)),
|
||||||
|
(-66, ('[NPC][Asteroid]Sanshas Nation', 5586, 4112, 0, 0)),
|
||||||
|
(-67, ('[NPC][Asteroid]Serpentis', 0, 5373, 4813, 0)),
|
||||||
|
(-68, ('[NPC][Burner]Cruor (Blood Raiders)', 90, 90, 0, 0)),
|
||||||
|
(-69, ('[NPC][Burner]Dramiel (Angel)', 55, 0, 20, 96)),
|
||||||
|
(-70, ('[NPC][Burner]Daredevil (Serpentis)', 0, 110, 154, 0)),
|
||||||
|
(-71, ('[NPC][Burner]Succubus (Sanshas Nation)', 135, 30, 0, 0)),
|
||||||
|
(-72, ('[NPC][Burner]Worm (Guristas)', 0, 0, 228, 0)),
|
||||||
|
(-73, ('[NPC][Burner]Enyo', 0, 147, 147, 0)),
|
||||||
|
(-74, ('[NPC][Burner]Hawk', 0, 0, 247, 0)),
|
||||||
|
(-75, ('[NPC][Burner]Jaguar', 36, 0, 50, 182)),
|
||||||
|
(-76, ('[NPC][Burner]Vengeance', 232, 0, 0, 0)),
|
||||||
|
(-77, ('[NPC][Burner]Ashimmu (Blood Raiders)', 260, 100, 0, 0)),
|
||||||
|
(-78, ('[NPC][Burner]Talos', 0, 413, 413, 0)),
|
||||||
|
(-79, ('[NPC][Burner]Sentinel', 0, 75, 0, 90)),
|
||||||
|
(-80, ('[NPC][Deadspace]Angel Cartel', 369, 533, 1395, 3302)),
|
||||||
|
(-81, ('[NPC][Deadspace]Blood Raiders', 6040, 5052, 10, 15)),
|
||||||
|
(-82, ('[NPC][Deadspace]Guristas', 0, 1531, 9680, 0)),
|
||||||
|
(-83, ('[NPC][Deadspace]Rogue Drone', 276, 1071, 1069, 871)),
|
||||||
|
(-84, ('[NPC][Deadspace]Sanshas Nation', 3009, 2237, 0, 0)),
|
||||||
|
(-85, ('[NPC][Deadspace]Serpentis', 0, 3110, 1929, 0)),
|
||||||
|
# Source: ticket #2067
|
||||||
|
(-86, ('[NPC][Invasion][Invading Precursor Entities]Dread', 0, 417, 0, 583)),
|
||||||
|
(-87, ('[NPC][Invasion][Invading Precursor Entities]Normal Subcaps', 0, 610, 0, 390)),
|
||||||
|
(-88, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 0% spool up', 367, 155, 367, 112)),
|
||||||
|
(-89, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 50% spool up', 291, 243, 291, 175)),
|
||||||
|
(-90, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 100% spool up', 241, 301, 241, 217)),
|
||||||
|
(-91, ('[NPC][Invasion][Retaliating Amarr Entities]Dread/Subcaps', 583, 417, 0, 0)),
|
||||||
|
(-92, ('[NPC][Invasion][Retaliating Caldari Entities]Dread', 1000, 0, 0, 0)),
|
||||||
|
(-93, ('[NPC][Invasion][Retaliating Caldari Entities]Subcaps', 511, 21, 29, 440)),
|
||||||
|
(-94, ('[NPC][Invasion][Retaliating Gallente Entities]Dread/Subcaps', 0, 417, 583, 0)),
|
||||||
|
(-95, ('[NPC][Invasion][Retaliating Minmatar Entities]Dread', 0, 0, 583, 417)),
|
||||||
|
(-96, ('[NPC][Invasion][Retaliating Minmatar Entities]Subcaps', 302, 136, 328, 234)),
|
||||||
|
(-97, ('[NPC][Mission]Amarr Empire', 4464, 3546, 97, 0)),
|
||||||
|
(-98, ('[NPC][Mission]Caldari State', 0, 2139, 4867, 0)),
|
||||||
|
(-99, ('[NPC][Mission]CONCORD', 336, 134, 212, 412)),
|
||||||
|
(-100, ('[NPC][Mission]Gallente Federation', 9, 3712, 2758, 0)),
|
||||||
|
(-101, ('[NPC][Mission]Khanid', 612, 483, 43, 6)),
|
||||||
|
(-102, ('[NPC][Mission]Minmatar Republic', 1024, 388, 1655, 4285)),
|
||||||
|
(-103, ('[NPC][Mission]Mordus Legion', 25, 262, 625, 0)),
|
||||||
|
(-104, ('[NPC][Mission]Thukker', 0, 52, 10, 79)),
|
||||||
|
(-105, ('[NPC]Sansha Incursion', 1682, 1347, 3678, 3678)),
|
||||||
|
(-106, ('[NPC]Sleepers', 1472, 1472, 1384, 1384))])
|
||||||
|
|
||||||
|
|
||||||
class DamagePattern:
|
class DamagePattern:
|
||||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
|
||||||
|
DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
|
||||||
|
_builtins = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.builtin = False
|
||||||
self.update(*args, **kwargs)
|
self.update(*args, **kwargs)
|
||||||
|
|
||||||
|
@reconstructor
|
||||||
|
def init(self):
|
||||||
|
self.builtin = False
|
||||||
|
|
||||||
def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
|
def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
|
||||||
self.emAmount = emAmount
|
self.emAmount = emAmount
|
||||||
self.thermalAmount = thermalAmount
|
self.thermalAmount = thermalAmount
|
||||||
self.kineticAmount = kineticAmount
|
self.kineticAmount = kineticAmount
|
||||||
self.explosiveAmount = explosiveAmount
|
self.explosiveAmount = explosiveAmount
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getBuiltinList(cls):
|
||||||
|
if cls._builtins is None:
|
||||||
|
cls.__generateBuiltins()
|
||||||
|
return list(cls._builtins.values())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getBuiltinById(cls, id):
|
||||||
|
if cls._builtins is None:
|
||||||
|
cls.__generateBuiltins()
|
||||||
|
return cls._builtins.get(id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getDefaultBuiltin(cls):
|
||||||
|
if cls._builtins is None:
|
||||||
|
cls.__generateBuiltins()
|
||||||
|
return cls._builtins.get(-1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __generateBuiltins(cls):
|
||||||
|
cls._builtins = OrderedDict()
|
||||||
|
for id, (rawName, em, therm, kin, explo) in BUILTINS.items():
|
||||||
|
pattern = DamagePattern(emAmount=em, thermalAmount=therm, kineticAmount=kin, explosiveAmount=explo)
|
||||||
|
pattern.ID = id
|
||||||
|
pattern.rawName = rawName
|
||||||
|
pattern.builtin = True
|
||||||
|
cls._builtins[id] = pattern
|
||||||
|
|
||||||
def calculateEhp(self, fit):
|
def calculateEhp(self, fit):
|
||||||
ehp = {}
|
ehp = {}
|
||||||
for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
|
for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
|
||||||
@@ -78,6 +231,15 @@ class DamagePattern:
|
|||||||
"exp" : "explosive"
|
"exp" : "explosive"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def oneType(cls, damageType, amount=100):
|
||||||
|
pattern = DamagePattern()
|
||||||
|
pattern.update(amount if damageType == "em" else 0,
|
||||||
|
amount if damageType == "thermal" else 0,
|
||||||
|
amount if damageType == "kinetic" else 0,
|
||||||
|
amount if damageType == "explosive" else 0)
|
||||||
|
return pattern
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def importPatterns(cls, text):
|
def importPatterns(cls, text):
|
||||||
lines = re.split('[\n\r]+', text)
|
lines = re.split('[\n\r]+', text)
|
||||||
@@ -89,7 +251,7 @@ class DamagePattern:
|
|||||||
lookup = {}
|
lookup = {}
|
||||||
current = eos.db.getDamagePatternList()
|
current = eos.db.getDamagePatternList()
|
||||||
for pattern in current:
|
for pattern in current:
|
||||||
lookup[pattern.name] = pattern
|
lookup[pattern.rawName] = pattern
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
try:
|
try:
|
||||||
@@ -98,6 +260,8 @@ class DamagePattern:
|
|||||||
line = line.split('#', 1)[0] # allows for comments
|
line = line.split('#', 1)[0] # allows for comments
|
||||||
type, data = line.rsplit('=', 1)
|
type, data = line.rsplit('=', 1)
|
||||||
type, data = type.strip(), data.split(',')
|
type, data = type.strip(), data.split(',')
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# Data isn't in correct format, continue to next line
|
# Data isn't in correct format, continue to next line
|
||||||
continue
|
continue
|
||||||
@@ -112,6 +276,8 @@ class DamagePattern:
|
|||||||
for index, val in enumerate(data):
|
for index, val in enumerate(data):
|
||||||
try:
|
try:
|
||||||
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
|
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -122,7 +288,7 @@ class DamagePattern:
|
|||||||
eos.db.save(pattern)
|
eos.db.save(pattern)
|
||||||
else:
|
else:
|
||||||
pattern = DamagePattern(**fields)
|
pattern = DamagePattern(**fields)
|
||||||
pattern.name = name.strip()
|
pattern.rawName = name.strip()
|
||||||
eos.db.save(pattern)
|
eos.db.save(pattern)
|
||||||
patterns.append(pattern)
|
patterns.append(pattern)
|
||||||
|
|
||||||
@@ -138,11 +304,41 @@ class DamagePattern:
|
|||||||
out += "# Values are in following format:\n"
|
out += "# Values are in following format:\n"
|
||||||
out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
|
out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
|
||||||
for dp in patterns:
|
for dp in patterns:
|
||||||
out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
|
out += cls.EXPORT_FORMAT % (dp.rawName, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
|
||||||
|
|
||||||
return out.strip()
|
return out.strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.rawName
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullName(self):
|
||||||
|
categories, tail = self.__parseRawName()
|
||||||
|
return '{}{}'.format(''.join('[{}]'.format(c) for c in categories), tail)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shortName(self):
|
||||||
|
return self.__parseRawName()[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hierarchy(self):
|
||||||
|
return self.__parseRawName()[0]
|
||||||
|
|
||||||
|
def __parseRawName(self):
|
||||||
|
categories = []
|
||||||
|
remainingName = self.rawName.strip() if self.rawName else ''
|
||||||
|
while True:
|
||||||
|
start, end = remainingName.find('['), remainingName.find(']')
|
||||||
|
if start == -1 or end == -1:
|
||||||
|
return categories, remainingName
|
||||||
|
splitter = remainingName.find('|')
|
||||||
|
if splitter != -1 and splitter == start - 1:
|
||||||
|
return categories, remainingName[1:]
|
||||||
|
categories.append(remainingName[start + 1:end])
|
||||||
|
remainingName = remainingName[end + 1:].strip()
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
p = DamagePattern(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
|
p = DamagePattern(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
|
||||||
p.name = "%s copy" % self.name
|
p.rawName = "%s copy" % self.rawName
|
||||||
return p
|
return p
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
from sqlalchemy.orm import reconstructor, validates
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ import eos.db
|
|||||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||||
from eos.utils.cycles import CycleInfo
|
from eos.utils.cycles import CycleInfo
|
||||||
|
from eos.utils.default import DEFAULT
|
||||||
from eos.utils.stats import DmgTypes, RRTypes
|
from eos.utils.stats import DmgTypes, RRTypes
|
||||||
|
|
||||||
|
|
||||||
@@ -45,6 +47,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.amount = 0
|
self.amount = 0
|
||||||
self.amountActive = 0
|
self.amountActive = 0
|
||||||
self.projected = False
|
self.projected = False
|
||||||
|
self.projectionRange = None
|
||||||
self.build()
|
self.build()
|
||||||
|
|
||||||
@reconstructor
|
@reconstructor
|
||||||
@@ -304,7 +307,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||||
if self.projected or forceProjected:
|
if self.projected or forceProjected:
|
||||||
context = "projected", "drone"
|
context = "projected", "drone"
|
||||||
projected = True
|
projected = True
|
||||||
@@ -312,6 +315,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
context = ("drone",)
|
context = ("drone",)
|
||||||
projected = False
|
projected = False
|
||||||
|
|
||||||
|
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||||
|
|
||||||
for effect in self.item.effects.values():
|
for effect in self.item.effects.values():
|
||||||
if effect.runTime == runTime and \
|
if effect.runTime == runTime and \
|
||||||
effect.activeByDefault and \
|
effect.activeByDefault and \
|
||||||
@@ -319,36 +324,34 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
projected is False and effect.isType("passive")):
|
projected is False and effect.isType("passive")):
|
||||||
# See GH issue #765
|
# See GH issue #765
|
||||||
if effect.getattr('grouped'):
|
if effect.getattr('grouped'):
|
||||||
try:
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
effect.handler(fit, self, context, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, context)
|
|
||||||
else:
|
else:
|
||||||
i = 0
|
i = 0
|
||||||
while i != self.amountActive:
|
while i != self.amountActive:
|
||||||
try:
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
effect.handler(fit, self, context, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, context)
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
if self.charge:
|
if self.charge:
|
||||||
for effect in self.charge.effects.values():
|
for effect in self.charge.effects.values():
|
||||||
if effect.runTime == runTime and effect.activeByDefault:
|
if effect.runTime == runTime and effect.activeByDefault:
|
||||||
effect.handler(fit, self, ("droneCharge",))
|
effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect)
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
copy = Drone(self.item)
|
copy = Drone(self.item)
|
||||||
copy.amount = self.amount
|
copy.amount = self.amount
|
||||||
copy.amountActive = self.amountActive
|
copy.amountActive = self.amountActive
|
||||||
|
copy.projectionRange = self.projectionRange
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def rebase(self, item):
|
def rebase(self, item):
|
||||||
amount = self.amount
|
amount = self.amount
|
||||||
amountActive = self.amountActive
|
amountActive = self.amountActive
|
||||||
|
projectionRange = self.projectionRange
|
||||||
|
|
||||||
Drone.__init__(self, item)
|
Drone.__init__(self, item)
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
self.amountActive = amountActive
|
self.amountActive = amountActive
|
||||||
|
self.projectionRange = projectionRange
|
||||||
|
|
||||||
def fits(self, fit):
|
def fits(self, fit):
|
||||||
fitDroneGroupLimits = set()
|
fitDroneGroupLimits = set()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
from sqlalchemy.orm import reconstructor, validates
|
||||||
|
|
||||||
@@ -27,8 +28,9 @@ from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
|||||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||||
from eos.saveddata.fighterAbility import FighterAbility
|
from eos.saveddata.fighterAbility import FighterAbility
|
||||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||||
from eos.utils.stats import DmgTypes
|
from eos.utils.default import DEFAULT
|
||||||
from eos.utils.float import floatUnerr
|
from eos.utils.float import floatUnerr
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
@@ -47,6 +49,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
self.itemID = item.ID if item is not None else None
|
self.itemID = item.ID if item is not None else None
|
||||||
self.projected = False
|
self.projected = False
|
||||||
|
self.projectionRange = None
|
||||||
self.active = True
|
self.active = True
|
||||||
|
|
||||||
# -1 is a placeholder that represents max squadron size, which we may not know yet as ships may modify this with
|
# -1 is a placeholder that represents max squadron size, which we may not know yet as ships may modify this with
|
||||||
@@ -96,7 +99,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||||
|
|
||||||
if len(self.abilities) != len(self.item.effects):
|
if {a.effectID for a in self.abilities} != {e.ID for e in self.item.effects.values()}:
|
||||||
self.__abilities = []
|
self.__abilities = []
|
||||||
for ability in self.__getAbilities():
|
for ability in self.__getAbilities():
|
||||||
self.__abilities.append(ability)
|
self.__abilities.append(ability)
|
||||||
@@ -380,7 +383,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||||
if not self.active:
|
if not self.active:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -391,6 +394,8 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
context = ("fighter",)
|
context = ("fighter",)
|
||||||
projected = False
|
projected = False
|
||||||
|
|
||||||
|
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||||
|
|
||||||
for ability in self.abilities:
|
for ability in self.abilities:
|
||||||
if not ability.active:
|
if not ability.active:
|
||||||
continue
|
continue
|
||||||
@@ -399,17 +404,11 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if effect.runTime == runTime and effect.activeByDefault and \
|
if effect.runTime == runTime and effect.activeByDefault and \
|
||||||
((projected and effect.isType("projected")) or not projected):
|
((projected and effect.isType("projected")) or not projected):
|
||||||
if ability.grouped:
|
if ability.grouped:
|
||||||
try:
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
effect.handler(fit, self, context, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, context)
|
|
||||||
else:
|
else:
|
||||||
i = 0
|
i = 0
|
||||||
while i != self.amount:
|
while i != self.amount:
|
||||||
try:
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
effect.handler(fit, self, context, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, context)
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
@@ -419,18 +418,22 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
for ability in self.abilities:
|
for ability in self.abilities:
|
||||||
copyAbility = next(filter(lambda a: a.effectID == ability.effectID, copy.abilities))
|
copyAbility = next(filter(lambda a: a.effectID == ability.effectID, copy.abilities))
|
||||||
copyAbility.active = ability.active
|
copyAbility.active = ability.active
|
||||||
|
copy.projectionRange = self.projectionRange
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
def rebase(self, item):
|
def rebase(self, item):
|
||||||
amount = self._amount
|
amount = self._amount
|
||||||
active = self.active
|
active = self.active
|
||||||
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
|
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
|
||||||
|
projectionRange = self.projectionRange
|
||||||
|
|
||||||
Fighter.__init__(self, item)
|
Fighter.__init__(self, item)
|
||||||
self._amount = amount
|
self._amount = amount
|
||||||
self.active = active
|
self.active = active
|
||||||
for ability in self.abilities:
|
for ability in self.abilities:
|
||||||
if ability.effectID in abilityEffectStates:
|
if ability.effectID in abilityEffectStates:
|
||||||
ability.active = abilityEffectStates[ability.effectID]
|
ability.active = abilityEffectStates[ability.effectID]
|
||||||
|
self.projectionRange = projectionRange
|
||||||
|
|
||||||
def fits(self, fit):
|
def fits(self, fit):
|
||||||
# If ships doesn't support this type of fighter, don't add it
|
# If ships doesn't support this type of fighter, don't add it
|
||||||
|
|||||||
@@ -21,21 +21,24 @@ import datetime
|
|||||||
import time
|
import time
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from math import log, sqrt
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
from math import asinh, log, sqrt
|
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
from sqlalchemy.orm import reconstructor, validates
|
||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
from eos import capSim
|
from eos import capSim
|
||||||
|
from eos.calc import calculateLockTime, calculateMultiplier
|
||||||
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
|
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
|
||||||
from eos.effectHandlerHelpers import (
|
from eos.effectHandlerHelpers import (
|
||||||
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
|
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
|
||||||
HandledModuleList, HandledProjectedDroneList, HandledProjectedModList)
|
HandledModuleList, HandledProjectedDroneList, HandledProjectedModList)
|
||||||
from eos.saveddata.character import Character
|
from eos.saveddata.character import Character
|
||||||
from eos.saveddata.citadel import Citadel
|
from eos.saveddata.citadel import Citadel
|
||||||
|
from eos.saveddata.damagePattern import DamagePattern
|
||||||
from eos.saveddata.module import Module
|
from eos.saveddata.module import Module
|
||||||
from eos.saveddata.ship import Ship
|
from eos.saveddata.ship import Ship
|
||||||
|
from eos.saveddata.targetProfile import TargetProfile
|
||||||
from eos.utils.stats import DmgTypes, RRTypes
|
from eos.utils.stats import DmgTypes, RRTypes
|
||||||
|
|
||||||
|
|
||||||
@@ -162,14 +165,29 @@ class Fit:
|
|||||||
self.__capUsed = None
|
self.__capUsed = None
|
||||||
self.__capRecharge = None
|
self.__capRecharge = None
|
||||||
self.__savedCapSimData.clear()
|
self.__savedCapSimData.clear()
|
||||||
|
# Ancillary tank modules affect this
|
||||||
|
self.__sustainableTank = None
|
||||||
|
self.__effectiveSustainableTank = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def targetProfile(self):
|
def targetProfile(self):
|
||||||
return self.__targetProfile
|
if self.__userTargetProfile is not None:
|
||||||
|
return self.__userTargetProfile
|
||||||
|
if self.__builtinTargetProfileID is not None:
|
||||||
|
return TargetProfile.getBuiltinById(self.__builtinTargetProfileID)
|
||||||
|
return None
|
||||||
|
|
||||||
@targetProfile.setter
|
@targetProfile.setter
|
||||||
def targetProfile(self, targetProfile):
|
def targetProfile(self, targetProfile):
|
||||||
self.__targetProfile = targetProfile
|
if targetProfile is None:
|
||||||
|
self.__userTargetProfile = None
|
||||||
|
self.__builtinTargetProfileID = None
|
||||||
|
elif targetProfile.builtin:
|
||||||
|
self.__userTargetProfile = None
|
||||||
|
self.__builtinTargetProfileID = targetProfile.ID
|
||||||
|
else:
|
||||||
|
self.__userTargetProfile = targetProfile
|
||||||
|
self.__builtinTargetProfileID = None
|
||||||
self.__weaponDpsMap = {}
|
self.__weaponDpsMap = {}
|
||||||
self.__weaponVolleyMap = {}
|
self.__weaponVolleyMap = {}
|
||||||
self.__droneDps = None
|
self.__droneDps = None
|
||||||
@@ -177,11 +195,25 @@ class Fit:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def damagePattern(self):
|
def damagePattern(self):
|
||||||
return self.__damagePattern
|
if self.__userDamagePattern is not None:
|
||||||
|
return self.__userDamagePattern
|
||||||
|
if self.__builtinDamagePatternID is not None:
|
||||||
|
pattern = DamagePattern.getBuiltinById(self.__builtinDamagePatternID)
|
||||||
|
if pattern is not None:
|
||||||
|
return pattern
|
||||||
|
return DamagePattern.getDefaultBuiltin()
|
||||||
|
|
||||||
@damagePattern.setter
|
@damagePattern.setter
|
||||||
def damagePattern(self, damagePattern):
|
def damagePattern(self, damagePattern):
|
||||||
self.__damagePattern = damagePattern
|
if damagePattern is None:
|
||||||
|
self.__userDamagePattern = None
|
||||||
|
self.__builtinDamagePatternID = None
|
||||||
|
elif damagePattern.builtin:
|
||||||
|
self.__userDamagePattern = None
|
||||||
|
self.__builtinDamagePatternID = damagePattern.ID
|
||||||
|
else:
|
||||||
|
self.__userDamagePattern = damagePattern
|
||||||
|
self.__builtinDamagePatternID = None
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.__effectiveTank = None
|
self.__effectiveTank = None
|
||||||
|
|
||||||
@@ -440,10 +472,8 @@ class Fit:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
||||||
if isinstance(self.ship, Citadel) and item.category.name != "Structure Module" or \
|
if isinstance(self.ship, Citadel) is not item.isStandup:
|
||||||
not isinstance(self.ship, Citadel) and item.category.name == "Structure Module":
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def clear(self, projected=False, command=False):
|
def clear(self, projected=False, command=False):
|
||||||
@@ -546,11 +576,15 @@ class Fit:
|
|||||||
|
|
||||||
if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
|
if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
|
||||||
self.modules.filteredItemBoost(
|
self.modules.filteredItemBoost(
|
||||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||||
"Shield Emission Systems"), "capacitorNeed", value)
|
mod.item.requiresSkill("Shield Emission Systems") or
|
||||||
|
mod.item.requiresSkill("Capital Shield Emission Systems"),
|
||||||
|
"capacitorNeed", value)
|
||||||
self.modules.filteredItemBoost(
|
self.modules.filteredItemBoost(
|
||||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||||
"Shield Emission Systems"), "duration", value)
|
mod.item.requiresSkill("Shield Emission Systems") or
|
||||||
|
mod.item.requiresSkill("Capital Shield Emission Systems"),
|
||||||
|
"duration", value)
|
||||||
|
|
||||||
if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP
|
if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP
|
||||||
self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True)
|
self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True)
|
||||||
@@ -560,12 +594,16 @@ class Fit:
|
|||||||
self.ship.boostItemAttr("armor%sDamageResonance" % damageType, value, stackingPenalties=True)
|
self.ship.boostItemAttr("armor%sDamageResonance" % damageType, value, stackingPenalties=True)
|
||||||
|
|
||||||
if warfareBuffID == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
|
if warfareBuffID == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
|
||||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
self.modules.filteredItemBoost(
|
||||||
mod.item.requiresSkill("Repair Systems"),
|
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||||
"capacitorNeed", value)
|
mod.item.requiresSkill("Repair Systems") or
|
||||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||||
mod.item.requiresSkill("Repair Systems"),
|
"capacitorNeed", value)
|
||||||
"duration", value)
|
self.modules.filteredItemBoost(
|
||||||
|
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||||
|
mod.item.requiresSkill("Repair Systems") or
|
||||||
|
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||||
|
"duration", value)
|
||||||
|
|
||||||
if warfareBuffID == 15: # Armor Burst: Armor Reinforcement: Armor HP
|
if warfareBuffID == 15: # Armor Burst: Armor Reinforcement: Armor HP
|
||||||
self.ship.boostItemAttr("armorHP", value, stackingPenalties=True)
|
self.ship.boostItemAttr("armorHP", value, stackingPenalties=True)
|
||||||
@@ -934,13 +972,20 @@ class Fit:
|
|||||||
To support a simpler way of doing self projections (so that we don't have to make a copy of the fit and
|
To support a simpler way of doing self projections (so that we don't have to make a copy of the fit and
|
||||||
recalculate), this function was developed to be a common source of projected effect application.
|
recalculate), this function was developed to be a common source of projected effect application.
|
||||||
"""
|
"""
|
||||||
c = chain(self.drones, self.fighters, self.modules)
|
for item in chain(self.drones, self.fighters):
|
||||||
for item in c:
|
|
||||||
if item is not None:
|
if item is not None:
|
||||||
# apply effects onto target fit x amount of times
|
# apply effects onto target fit x amount of times
|
||||||
for _ in range(projectionInfo.amount):
|
for _ in range(projectionInfo.amount):
|
||||||
targetFit.register(item, origin=self)
|
targetFit.register(item, origin=self)
|
||||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
item.calculateModifiedAttributes(
|
||||||
|
targetFit, runTime, forceProjected=True,
|
||||||
|
forcedProjRange=0)
|
||||||
|
for mod in self.modules:
|
||||||
|
for _ in range(projectionInfo.amount):
|
||||||
|
targetFit.register(mod, origin=self)
|
||||||
|
mod.calculateModifiedAttributes(
|
||||||
|
targetFit, runTime, forceProjected=True,
|
||||||
|
forcedProjRange=projectionInfo.projectionRange)
|
||||||
|
|
||||||
def fill(self):
|
def fill(self):
|
||||||
"""
|
"""
|
||||||
@@ -981,6 +1026,16 @@ class Fit:
|
|||||||
if mod.isEmpty:
|
if mod.isEmpty:
|
||||||
del self.modules[i]
|
del self.modules[i]
|
||||||
|
|
||||||
|
def clearTail(self):
|
||||||
|
tailPositions = {}
|
||||||
|
for mod in reversed(self.modules):
|
||||||
|
if not mod.isEmpty:
|
||||||
|
break
|
||||||
|
tailPositions[self.modules.index(mod)] = mod.slot
|
||||||
|
for pos in sorted(tailPositions, reverse=True):
|
||||||
|
self.modules.remove(self.modules[pos])
|
||||||
|
return tailPositions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def modCount(self):
|
def modCount(self):
|
||||||
x = 0
|
x = 0
|
||||||
@@ -1090,7 +1145,7 @@ class Fit:
|
|||||||
def droneBayUsed(self):
|
def droneBayUsed(self):
|
||||||
amount = 0
|
amount = 0
|
||||||
for d in self.drones:
|
for d in self.drones:
|
||||||
amount += d.item.volume * d.amount
|
amount += d.item.attributes['volume'].value * d.amount
|
||||||
|
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
@@ -1098,7 +1153,7 @@ class Fit:
|
|||||||
def fighterBayUsed(self):
|
def fighterBayUsed(self):
|
||||||
amount = 0
|
amount = 0
|
||||||
for f in self.fighters:
|
for f in self.fighters:
|
||||||
amount += f.item.volume * f.amount
|
amount += f.item.attributes['volume'].value * f.amount
|
||||||
|
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
@@ -1219,8 +1274,8 @@ class Fit:
|
|||||||
# Signature reduction, uses the bomb formula as per CCP Larrikin
|
# Signature reduction, uses the bomb formula as per CCP Larrikin
|
||||||
if energyNeutralizerSignatureResolution:
|
if energyNeutralizerSignatureResolution:
|
||||||
capNeed = capNeed * min(1, signatureRadius / energyNeutralizerSignatureResolution)
|
capNeed = capNeed * min(1, signatureRadius / energyNeutralizerSignatureResolution)
|
||||||
|
if capNeed:
|
||||||
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
||||||
|
|
||||||
def removeDrain(self, i):
|
def removeDrain(self, i):
|
||||||
del self.__extraDrains[i]
|
del self.__extraDrains[i]
|
||||||
@@ -1320,8 +1375,8 @@ class Fit:
|
|||||||
"""Return how much cap regen do we gain from having this module"""
|
"""Return how much cap regen do we gain from having this module"""
|
||||||
currentRegen = self.calculateCapRecharge()
|
currentRegen = self.calculateCapRecharge()
|
||||||
nomodRegen = self.calculateCapRecharge(
|
nomodRegen = self.calculateCapRecharge(
|
||||||
capacity=self.ship.getModifiedItemAttrWithoutAfflictor("capacitorCapacity", mod),
|
capacity=self.ship.getModifiedItemAttrExtended("capacitorCapacity", ignoreAfflictors=[mod]),
|
||||||
rechargeRate=self.ship.getModifiedItemAttrWithoutAfflictor("rechargeRate", mod) / 1000.0)
|
rechargeRate=self.ship.getModifiedItemAttrExtended("rechargeRate", ignoreAfflictors=[mod]) / 1000.0)
|
||||||
return currentRegen - nomodRegen
|
return currentRegen - nomodRegen
|
||||||
|
|
||||||
def getRemoteReps(self, spoolOptions=None):
|
def getRemoteReps(self, spoolOptions=None):
|
||||||
@@ -1524,9 +1579,7 @@ class Fit:
|
|||||||
def calculateLockTime(self, radius):
|
def calculateLockTime(self, radius):
|
||||||
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
||||||
if scanRes is not None and scanRes > 0:
|
if scanRes is not None and scanRes > 0:
|
||||||
# Yes, this function returns time in seconds, not miliseconds.
|
return calculateLockTime(srcScanRes=scanRes, tgtSigRadius=radius)
|
||||||
# 40,000 is indeed the correct constant here.
|
|
||||||
return min(40000 / scanRes / asinh(radius) ** 2, 30 * 60)
|
|
||||||
else:
|
else:
|
||||||
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
|
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
|
||||||
|
|
||||||
@@ -1621,6 +1674,22 @@ class Fit:
|
|||||||
if ability.active:
|
if ability.active:
|
||||||
yield fighter, ability
|
yield fighter, ability
|
||||||
|
|
||||||
|
def getDampMultScanRes(self):
|
||||||
|
damps = []
|
||||||
|
for mod in self.activeModulesIter():
|
||||||
|
for effectName in ('remoteSensorDampFalloff', 'structureModuleEffectRemoteSensorDampener'):
|
||||||
|
if effectName in mod.item.effects:
|
||||||
|
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
|
||||||
|
if 'doomsdayAOEDamp' in mod.item.effects:
|
||||||
|
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
|
||||||
|
for drone in self.activeDronesIter():
|
||||||
|
if 'remoteSensorDampEntity' in drone.item.effects:
|
||||||
|
damps.extend(drone.amountActive * ((drone.getModifiedItemAttr('scanResolutionBonus'), 'default'),))
|
||||||
|
mults = {}
|
||||||
|
for strength, stackingGroup in damps:
|
||||||
|
mults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||||
|
return calculateMultiplier(mults)
|
||||||
|
|
||||||
def __deepcopy__(self, memo=None):
|
def __deepcopy__(self, memo=None):
|
||||||
fitCopy = Fit()
|
fitCopy = Fit()
|
||||||
# Character and owner are not copied
|
# Character and owner are not copied
|
||||||
@@ -1671,6 +1740,8 @@ class Fit:
|
|||||||
copyProjectionInfo = fit.getProjectionInfo(fitCopy.ID)
|
copyProjectionInfo = fit.getProjectionInfo(fitCopy.ID)
|
||||||
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
||||||
copyProjectionInfo.active = originalProjectionInfo.active
|
copyProjectionInfo.active = originalProjectionInfo.active
|
||||||
|
copyProjectionInfo.amount = originalProjectionInfo.amount
|
||||||
|
copyProjectionInfo.projectionRange = originalProjectionInfo.projectionRange
|
||||||
forceUpdateSavedata(fit)
|
forceUpdateSavedata(fit)
|
||||||
|
|
||||||
return fitCopy
|
return fitCopy
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Implant(HandledItem, ItemAttrShortcut):
|
|||||||
return
|
return
|
||||||
for effect in self.item.effects.values():
|
for effect in self.item.effects.values():
|
||||||
if effect.runTime == runTime and effect.isType("passive") and effect.activeByDefault:
|
if effect.runTime == runTime and effect.isType("passive") and effect.activeByDefault:
|
||||||
effect.handler(fit, self, ("implant",))
|
effect.handler(fit, self, ("implant",), None, effect=effect)
|
||||||
|
|
||||||
@validates("fitID", "itemID", "active")
|
@validates("fitID", "itemID", "active")
|
||||||
def validator(self, key, val):
|
def validator(self, key, val):
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Mode(ItemAttrShortcut, HandledItem):
|
|||||||
if self.item:
|
if self.item:
|
||||||
for effect in self.item.effects.values():
|
for effect in self.item.effects.values():
|
||||||
if effect.runTime == runTime and effect.activeByDefault:
|
if effect.runTime == runTime and effect.activeByDefault:
|
||||||
effect.handler(fit, self, context=("module",))
|
effect.handler(fit, self, ("module",), None, effect=effect)
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
copy = Mode(self.item)
|
copy = Mode(self.item)
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from logbook import Logger
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
from logbook import Logger
|
||||||
from sqlalchemy.orm import reconstructor, validates
|
from sqlalchemy.orm import reconstructor, validates
|
||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
@@ -28,6 +29,7 @@ from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, Modi
|
|||||||
from eos.saveddata.citadel import Citadel
|
from eos.saveddata.citadel import Citadel
|
||||||
from eos.saveddata.mutator import Mutator
|
from eos.saveddata.mutator import Mutator
|
||||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||||
|
from eos.utils.default import DEFAULT
|
||||||
from eos.utils.float import floatUnerr
|
from eos.utils.float import floatUnerr
|
||||||
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||||
from eos.utils.stats import DmgTypes, RRTypes
|
from eos.utils.stats import DmgTypes, RRTypes
|
||||||
@@ -90,6 +92,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.__charge = None
|
self.__charge = None
|
||||||
|
|
||||||
self.projected = False
|
self.projected = False
|
||||||
|
self.projectionRange = None
|
||||||
self.state = FittingModuleState.ONLINE
|
self.state = FittingModuleState.ONLINE
|
||||||
self.build()
|
self.build()
|
||||||
|
|
||||||
@@ -211,8 +214,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if charge is None:
|
if charge is None:
|
||||||
charges = 0
|
charges = 0
|
||||||
else:
|
else:
|
||||||
chargeVolume = charge.volume
|
chargeVolume = charge.attributes['volume'].value
|
||||||
containerCapacity = self.item.capacity
|
containerCapacity = self.item.attributes['capacity'].value
|
||||||
if chargeVolume is None or containerCapacity is None:
|
if chargeVolume is None or containerCapacity is None:
|
||||||
charges = 0
|
charges = 0
|
||||||
else:
|
else:
|
||||||
@@ -315,31 +318,68 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
"energyDestabilizationRange", "empFieldRange",
|
"energyDestabilizationRange", "empFieldRange",
|
||||||
"ecmBurstRange", "warpScrambleRange", "cargoScanRange",
|
"ecmBurstRange", "warpScrambleRange", "cargoScanRange",
|
||||||
"shipScanRange", "surveyScanRange")
|
"shipScanRange", "surveyScanRange")
|
||||||
|
maxRange = None
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
maxRange = self.getModifiedItemAttr(attr, None)
|
maxRange = self.getModifiedItemAttr(attr, None)
|
||||||
if maxRange is not None:
|
if maxRange is not None:
|
||||||
return maxRange
|
break
|
||||||
if self.charge is not None:
|
if maxRange is not None:
|
||||||
try:
|
if 'burst projector' in self.item.name.lower():
|
||||||
chargeName = self.charge.group.name
|
maxRange -= self.owner.ship.getModifiedItemAttr("radius")
|
||||||
except AttributeError:
|
return maxRange
|
||||||
pass
|
missileMaxRangeData = self.missileMaxRangeData
|
||||||
else:
|
if missileMaxRangeData is None:
|
||||||
if chargeName in ("Scanner Probe", "Survey Probe"):
|
return None
|
||||||
return None
|
lowerRange, higherRange, higherChance = missileMaxRangeData
|
||||||
|
maxRange = lowerRange * (1 - higherChance) + higherRange * higherChance
|
||||||
|
return maxRange
|
||||||
|
|
||||||
|
@property
|
||||||
|
def missileMaxRangeData(self):
|
||||||
|
if self.charge is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
chargeName = self.charge.group.name
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if chargeName in ("Scanner Probe", "Survey Probe"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def calculateRange(maxVelocity, mass, agility, flightTime):
|
||||||
# Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
|
# Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
|
||||||
# D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
|
# D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
|
||||||
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
|
accelTime = min(flightTime, mass * agility / 1000000)
|
||||||
flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
|
# Average distance done during acceleration
|
||||||
mass = self.getModifiedChargeAttr("mass")
|
duringAcceleration = maxVelocity / 2 * accelTime
|
||||||
agility = self.getModifiedChargeAttr("agility")
|
# Distance done after being at full speed
|
||||||
if maxVelocity and (flightTime or mass or agility):
|
fullSpeed = maxVelocity * (flightTime - accelTime)
|
||||||
accelTime = min(flightTime, mass * agility / 1000000)
|
maxRange = duringAcceleration + fullSpeed
|
||||||
# Average distance done during acceleration
|
return maxRange
|
||||||
duringAcceleration = maxVelocity / 2 * accelTime
|
|
||||||
# Distance done after being at full speed
|
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
|
||||||
fullSpeed = maxVelocity * (flightTime - accelTime)
|
if not maxVelocity:
|
||||||
return duringAcceleration + fullSpeed
|
return None
|
||||||
|
shipRadius = self.owner.ship.getModifiedItemAttr("radius")
|
||||||
|
# Flight time has bonus based on ship radius, see https://github.com/pyfa-org/Pyfa/issues/2083
|
||||||
|
flightTime = floatUnerr(self.getModifiedChargeAttr("explosionDelay") / 1000 + shipRadius / maxVelocity)
|
||||||
|
mass = self.getModifiedChargeAttr("mass")
|
||||||
|
agility = self.getModifiedChargeAttr("agility")
|
||||||
|
lowerTime = math.floor(flightTime)
|
||||||
|
higherTime = math.ceil(flightTime)
|
||||||
|
lowerRange = calculateRange(maxVelocity, mass, agility, lowerTime)
|
||||||
|
higherRange = calculateRange(maxVelocity, mass, agility, higherTime)
|
||||||
|
# Fof range limit is supposedly calculated based on overview (surface-to-surface) range
|
||||||
|
if 'fofMissileLaunching' in self.charge.effects:
|
||||||
|
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
|
||||||
|
if rangeLimit:
|
||||||
|
lowerRange = min(lowerRange, rangeLimit)
|
||||||
|
higherRange = min(higherRange, rangeLimit)
|
||||||
|
# Make range center-to-surface, as missiles spawn in the center of the ship
|
||||||
|
lowerRange = max(0, lowerRange - shipRadius)
|
||||||
|
higherRange = max(0, higherRange - shipRadius)
|
||||||
|
higherChance = flightTime - lowerTime
|
||||||
|
return lowerRange, higherRange, higherChance
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def falloff(self):
|
def falloff(self):
|
||||||
@@ -428,7 +468,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.__baseVolley = {}
|
self.__baseVolley = {}
|
||||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||||
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0) or self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
# Some delay attributes have non-0 default value, so we have to pick according to effects
|
||||||
|
if {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(self.item.effects):
|
||||||
|
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0)
|
||||||
|
elif {'doomsdayBeamDOT', 'doomsdaySlash', 'doomsdayConeDOT'}.intersection(self.item.effects):
|
||||||
|
dmgDelay = self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
||||||
|
else:
|
||||||
|
dmgDelay = 0
|
||||||
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
|
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
|
||||||
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
|
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
|
||||||
# Reaper DD can damage each target only once
|
# Reaper DD can damage each target only once
|
||||||
@@ -650,7 +696,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
# Check this only if we're told to do so
|
# Check this only if we're told to do so
|
||||||
if hardpointLimit:
|
if hardpointLimit:
|
||||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -732,8 +778,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
# Check sizes, if 'charge size > module volume' it won't fit
|
# Check sizes, if 'charge size > module volume' it won't fit
|
||||||
if charge is None:
|
if charge is None:
|
||||||
return True
|
return True
|
||||||
chargeVolume = charge.volume
|
chargeVolume = charge.attributes['volume'].value
|
||||||
moduleCapacity = self.item.capacity
|
moduleCapacity = self.item.attributes['capacity'].value
|
||||||
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -826,7 +872,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
self.itemModifiedAttributes.clear()
|
self.itemModifiedAttributes.clear()
|
||||||
self.chargeModifiedAttributes.clear()
|
self.chargeModifiedAttributes.clear()
|
||||||
|
|
||||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False):
|
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False, forcedProjRange=DEFAULT):
|
||||||
# We will run the effect when two conditions are met:
|
# We will run the effect when two conditions are met:
|
||||||
# 1: It makes sense to run the effect
|
# 1: It makes sense to run the effect
|
||||||
# The effect is either offline
|
# The effect is either offline
|
||||||
@@ -843,6 +889,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
context = ("module",)
|
context = ("module",)
|
||||||
projected = False
|
projected = False
|
||||||
|
|
||||||
|
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||||
|
|
||||||
if self.charge is not None:
|
if self.charge is not None:
|
||||||
# fix for #82 and it's regression #106
|
# fix for #82 and it's regression #106
|
||||||
if not projected or (self.projected and not forceProjected) or gang:
|
if not projected or (self.projected and not forceProjected) or gang:
|
||||||
@@ -856,13 +904,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
(not gang or (gang and effect.isType("gang")))
|
(not gang or (gang and effect.isType("gang")))
|
||||||
):
|
):
|
||||||
contexts = ("moduleCharge",)
|
contexts = ("moduleCharge",)
|
||||||
# For gang effects, we pass in the effect itself as an argument. However, to avoid going through all
|
effect.handler(fit, self, contexts, projectionRange, effect=effect)
|
||||||
# the effect definitions and defining this argument, do a simple try/catch here and be done with it.
|
|
||||||
# @todo: possibly fix this
|
|
||||||
try:
|
|
||||||
effect.handler(fit, self, contexts, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, contexts)
|
|
||||||
|
|
||||||
if self.item:
|
if self.item:
|
||||||
if self.state >= FittingModuleState.OVERHEATED:
|
if self.state >= FittingModuleState.OVERHEATED:
|
||||||
@@ -872,7 +914,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
and not forceProjected \
|
and not forceProjected \
|
||||||
and effect.activeByDefault \
|
and effect.activeByDefault \
|
||||||
and ((gang and effect.isType("gang")) or not gang):
|
and ((gang and effect.isType("gang")) or not gang):
|
||||||
effect.handler(fit, self, context)
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
|
|
||||||
for effect in self.item.effects.values():
|
for effect in self.item.effects.values():
|
||||||
if effect.runTime == runTime and \
|
if effect.runTime == runTime and \
|
||||||
@@ -882,10 +924,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
|
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
|
||||||
and ((projected and effect.isType("projected")) or not projected) \
|
and ((projected and effect.isType("projected")) or not projected) \
|
||||||
and ((gang and effect.isType("gang")) or not gang):
|
and ((gang and effect.isType("gang")) or not gang):
|
||||||
try:
|
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||||
effect.handler(fit, self, context, effect=effect)
|
|
||||||
except:
|
|
||||||
effect.handler(fit, self, context)
|
|
||||||
|
|
||||||
def getCycleParameters(self, reloadOverride=None):
|
def getCycleParameters(self, reloadOverride=None):
|
||||||
"""Copied from new eos as well"""
|
"""Copied from new eos as well"""
|
||||||
@@ -1016,6 +1055,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
copy.state = self.state
|
copy.state = self.state
|
||||||
copy.spoolType = self.spoolType
|
copy.spoolType = self.spoolType
|
||||||
copy.spoolAmount = self.spoolAmount
|
copy.spoolAmount = self.spoolAmount
|
||||||
|
copy.projectionRange = self.projectionRange
|
||||||
|
|
||||||
for x in self.mutators.values():
|
for x in self.mutators.values():
|
||||||
Mutator(copy, x.attribute, x.value)
|
Mutator(copy, x.attribute, x.value)
|
||||||
@@ -1025,10 +1065,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def rebase(self, item):
|
def rebase(self, item):
|
||||||
state = self.state
|
state = self.state
|
||||||
charge = self.charge
|
charge = self.charge
|
||||||
|
spoolType = self.spoolType
|
||||||
|
spoolAmount = self.spoolAmount
|
||||||
|
projectionRange = self.projectionRange
|
||||||
|
|
||||||
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
|
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
|
||||||
self.state = state
|
self.state = state
|
||||||
if self.isValidCharge(charge):
|
if self.isValidCharge(charge):
|
||||||
self.charge = charge
|
self.charge = charge
|
||||||
|
self.spoolType = spoolType
|
||||||
|
self.spoolAmount = spoolAmount
|
||||||
|
self.projectionRange = projectionRange
|
||||||
for x in self.mutators.values():
|
for x in self.mutators.values():
|
||||||
Mutator(self, x.attribute, x.value)
|
Mutator(self, x.attribute, x.value)
|
||||||
|
|
||||||
|
|||||||
@@ -73,12 +73,16 @@ class Mutator(EqBase):
|
|||||||
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
|
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
|
||||||
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
|
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
|
||||||
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
|
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
self.module = None
|
self.module = None
|
||||||
|
|
||||||
@validates("value")
|
@validates("value")
|
||||||
def validator(self, key, val):
|
def validator(self, key, val):
|
||||||
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||||
|
if self.baseValue == 0:
|
||||||
|
return 0
|
||||||
mod = val / self.baseValue
|
mod = val / self.baseValue
|
||||||
|
|
||||||
if self.minMod <= mod <= self.maxMod:
|
if self.minMod <= mod <= self.maxMod:
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
|||||||
# skillbook modifiers will use the stale modifier value
|
# skillbook modifiers will use the stale modifier value
|
||||||
# GH issue #351
|
# GH issue #351
|
||||||
fit.register(self)
|
fit.register(self)
|
||||||
effect.handler(fit, self, ("ship",))
|
effect.handler(fit, self, ("ship",), None, effect=effect)
|
||||||
|
|
||||||
def validateModeItem(self, item, owner=None):
|
def validateModeItem(self, item, owner=None):
|
||||||
""" Checks if provided item is a valid mode """
|
""" Checks if provided item is a valid mode """
|
||||||
|
|||||||
@@ -19,8 +19,10 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
import eos.db
|
import eos.db
|
||||||
|
|
||||||
@@ -28,13 +30,173 @@ import eos.db
|
|||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
BUILTINS = OrderedDict([
|
||||||
|
# 0 is taken by ideal target profile, composed manually in one of TargetProfile methods
|
||||||
|
(-1, ('Uniform (25%)', 0.25, 0.25, 0.25, 0.25)),
|
||||||
|
(-2, ('Uniform (50%)', 0.50, 0.50, 0.50, 0.50)),
|
||||||
|
(-3, ('Uniform (75%)', 0.75, 0.75, 0.75, 0.75)),
|
||||||
|
(-4, ('Uniform (90%)', 0.90, 0.90, 0.90, 0.90)),
|
||||||
|
(-5, ('[T1 Resist]Shield', 0.0, 0.20, 0.40, 0.50)),
|
||||||
|
(-6, ('[T1 Resist]Armor', 0.50, 0.45, 0.25, 0.10)),
|
||||||
|
(-7, ('[T1 Resist]Hull', 0.33, 0.33, 0.33, 0.33)),
|
||||||
|
(-8, ('[T1 Resist]Shield (+T2 DCU)', 0.125, 0.30, 0.475, 0.562)),
|
||||||
|
(-9, ('[T1 Resist]Armor (+T2 DCU)', 0.575, 0.532, 0.363, 0.235)),
|
||||||
|
(-10, ('[T1 Resist]Hull (+T2 DCU)', 0.598, 0.598, 0.598, 0.598)),
|
||||||
|
(-11, ('[T2 Resist]Amarr (Shield)', 0.0, 0.20, 0.70, 0.875)),
|
||||||
|
(-12, ('[T2 Resist]Amarr (Armor)', 0.50, 0.35, 0.625, 0.80)),
|
||||||
|
(-13, ('[T2 Resist]Caldari (Shield)', 0.20, 0.84, 0.76, 0.60)),
|
||||||
|
(-14, ('[T2 Resist]Caldari (Armor)', 0.50, 0.8625, 0.625, 0.10)),
|
||||||
|
(-15, ('[T2 Resist]Gallente (Shield)', 0.0, 0.60, 0.85, 0.50)),
|
||||||
|
(-16, ('[T2 Resist]Gallente (Armor)', 0.50, 0.675, 0.8375, 0.10)),
|
||||||
|
(-17, ('[T2 Resist]Minmatar (Shield)', 0.75, 0.60, 0.40, 0.50)),
|
||||||
|
(-18, ('[T2 Resist]Minmatar (Armor)', 0.90, 0.675, 0.25, 0.10)),
|
||||||
|
(-19, ('[NPC][Asteroid]Angel Cartel', 0.54, 0.42, 0.37, 0.32)),
|
||||||
|
(-20, ('[NPC][Asteroid]Blood Raiders', 0.34, 0.39, 0.45, 0.52)),
|
||||||
|
(-21, ('[NPC][Asteroid]Guristas', 0.55, 0.35, 0.3, 0.48)),
|
||||||
|
(-22, ('[NPC][Asteroid]Rogue Drones', 0.35, 0.38, 0.44, 0.49)),
|
||||||
|
(-23, ('[NPC][Asteroid]Sanshas Nation', 0.35, 0.4, 0.47, 0.53)),
|
||||||
|
(-24, ('[NPC][Asteroid]Serpentis', 0.49, 0.38, 0.29, 0.51)),
|
||||||
|
(-25, ('[NPC][Deadspace]Angel Cartel', 0.59, 0.48, 0.4, 0.32)),
|
||||||
|
(-26, ('[NPC][Deadspace]Blood Raiders', 0.31, 0.39, 0.47, 0.56)),
|
||||||
|
(-27, ('[NPC][Deadspace]Guristas', 0.57, 0.39, 0.31, 0.5)),
|
||||||
|
(-28, ('[NPC][Deadspace]Rogue Drones', 0.42, 0.42, 0.47, 0.49)),
|
||||||
|
(-29, ('[NPC][Deadspace]Sanshas Nation', 0.31, 0.39, 0.47, 0.56)),
|
||||||
|
(-30, ('[NPC][Deadspace]Serpentis', 0.49, 0.38, 0.29, 0.56)),
|
||||||
|
(-31, ('[NPC][Mission]Amarr Empire', 0.34, 0.38, 0.42, 0.46)),
|
||||||
|
(-32, ('[NPC][Mission]Caldari State', 0.51, 0.38, 0.3, 0.51)),
|
||||||
|
(-33, ('[NPC][Mission]CONCORD', 0.47, 0.46, 0.47, 0.47)),
|
||||||
|
(-34, ('[NPC][Mission]Gallente Federation', 0.51, 0.38, 0.31, 0.52)),
|
||||||
|
(-35, ('[NPC][Mission]Khanid', 0.51, 0.42, 0.36, 0.4)),
|
||||||
|
(-36, ('[NPC][Mission]Minmatar Republic', 0.51, 0.46, 0.41, 0.35)),
|
||||||
|
(-37, ('[NPC][Mission]Mordus Legion', 0.32, 0.48, 0.4, 0.62)),
|
||||||
|
(-38, ('[NPC][Other]Sleeper', 0.61, 0.61, 0.61, 0.61)),
|
||||||
|
(-39, ('[NPC][Other]Sansha Incursion', 0.65, 0.63, 0.64, 0.65)),
|
||||||
|
(-40, ('[NPC][Burner]Cruor (Blood Raiders)', 0.8, 0.73, 0.69, 0.67)),
|
||||||
|
(-41, ('[NPC][Burner]Dramiel (Angel)', 0.35, 0.48, 0.61, 0.68)),
|
||||||
|
(-42, ('[NPC][Burner]Daredevil (Serpentis)', 0.69, 0.59, 0.59, 0.43)),
|
||||||
|
(-43, ('[NPC][Burner]Succubus (Sanshas Nation)', 0.35, 0.48, 0.61, 0.68)),
|
||||||
|
(-44, ('[NPC][Burner]Worm (Guristas)', 0.48, 0.58, 0.69, 0.74)),
|
||||||
|
(-45, ('[NPC][Burner]Enyo', 0.58, 0.72, 0.86, 0.24)),
|
||||||
|
(-46, ('[NPC][Burner]Hawk', 0.3, 0.86, 0.79, 0.65)),
|
||||||
|
(-47, ('[NPC][Burner]Jaguar', 0.78, 0.65, 0.48, 0.56)),
|
||||||
|
(-48, ('[NPC][Burner]Vengeance', 0.66, 0.56, 0.75, 0.86)),
|
||||||
|
(-49, ('[NPC][Burner]Ashimmu (Blood Raiders)', 0.8, 0.76, 0.68, 0.7)),
|
||||||
|
(-50, ('[NPC][Burner]Talos', 0.68, 0.59, 0.59, 0.43)),
|
||||||
|
(-51, ('[NPC][Burner]Sentinel', 0.58, 0.45, 0.52, 0.66)),
|
||||||
|
# Source: ticket #2067
|
||||||
|
(-52, ('[NPC][Invasion]Invading Precursor Entities', 0.422, 0.367, 0.453, 0.411)),
|
||||||
|
(-53, ('[NPC][Invasion]Retaliating Amarr Entities', 0.360, 0.310, 0.441, 0.602)),
|
||||||
|
(-54, ('[NPC][Invasion]Retaliating Caldari Entities', 0.287, 0.610, 0.487, 0.401)),
|
||||||
|
(-55, ('[NPC][Invasion]Retaliating Gallente Entities', 0.383, 0.414, 0.578, 0.513)),
|
||||||
|
(-56, ('[NPC][Invasion]Retaliating Minmatar Entities', 0.620, 0.422, 0.355, 0.399)),
|
||||||
|
(-57, ('[NPC][Abyssal][Dark Matter All Tiers]Drones', 0.439, 0.522, 0.529, 0.435)),
|
||||||
|
(-58, ('[NPC][Abyssal][Dark Matter All Tiers]Overmind', 0.626, 0.576, 0.612, 0.624)),
|
||||||
|
(-59, ('[NPC][Abyssal][Dark Matter All Tiers]Seeker', 0.082, 0.082, 0.082, 0.082)),
|
||||||
|
(-60, ('[NPC][Abyssal][Dark Matter All Tiers]Triglavian', 0.477, 0.401, 0.449, 0.37)),
|
||||||
|
(-61, ('[NPC][Abyssal][Dark Matter All Tiers]Drifter', 0.403, 0.403, 0.403, 0.403)),
|
||||||
|
(-62, ('[NPC][Abyssal][Dark Matter All Tiers]Sleeper', 0.435, 0.435, 0.435, 0.435)),
|
||||||
|
(-63, ('[NPC][Abyssal][Dark Matter All Tiers]All', 0.507, 0.477, 0.502, 0.493)),
|
||||||
|
(-64, ('[NPC][Abyssal][Electrical T1/T2]Drones', 0.323, 0.522, 0.529, 0.435)),
|
||||||
|
(-65, ('[NPC][Abyssal][Electrical T1/T2]Overmind', 0.521, 0.576, 0.612, 0.624)),
|
||||||
|
(-66, ('[NPC][Abyssal][Electrical T1/T2]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||||
|
(-67, ('[NPC][Abyssal][Electrical T1/T2]Triglavian', 0.333, 0.401, 0.449, 0.37)),
|
||||||
|
(-68, ('[NPC][Abyssal][Electrical T1/T2]Drifter', 0.267, 0.403, 0.403, 0.403)),
|
||||||
|
(-69, ('[NPC][Abyssal][Electrical T1/T2]Sleeper', 0.329, 0.435, 0.435, 0.435)),
|
||||||
|
(-70, ('[NPC][Abyssal][Electrical T1/T2]All', 0.385, 0.477, 0.502, 0.493)),
|
||||||
|
(-71, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drones', 0.255, 0.522, 0.529, 0.435)),
|
||||||
|
(-72, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Overmind', 0.457, 0.576, 0.612, 0.624)),
|
||||||
|
(-73, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||||
|
(-74, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Triglavian', 0.241, 0.401, 0.449, 0.37)),
|
||||||
|
(-75, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drifter', 0.184, 0.403, 0.403, 0.403)),
|
||||||
|
(-76, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Sleeper', 0.268, 0.435, 0.435, 0.435)),
|
||||||
|
(-77, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]All', 0.313, 0.477, 0.502, 0.493)),
|
||||||
|
(-78, ('[NPC][Abyssal][Electrical T4/T5]Drones', 0.193, 0.522, 0.529, 0.435)),
|
||||||
|
(-79, ('[NPC][Abyssal][Electrical T4/T5]Overmind', 0.398, 0.576, 0.612, 0.624)),
|
||||||
|
(-80, ('[NPC][Abyssal][Electrical T4/T5]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||||
|
(-81, ('[NPC][Abyssal][Electrical T4/T5]Triglavian', 0.183, 0.401, 0.449, 0.37)),
|
||||||
|
(-82, ('[NPC][Abyssal][Electrical T4/T5]Drifter', 0.107, 0.403, 0.403, 0.403)),
|
||||||
|
(-83, ('[NPC][Abyssal][Electrical T4/T5]Sleeper', 0.215, 0.435, 0.435, 0.435)),
|
||||||
|
(-84, ('[NPC][Abyssal][Electrical T4/T5]All', 0.25, 0.477, 0.502, 0.493)),
|
||||||
|
(-85, ('[NPC][Abyssal][Firestorm T1/T2]Drones', 0.461, 0.425, 0.541, 0.443)),
|
||||||
|
(-86, ('[NPC][Abyssal][Firestorm T1/T2]Overmind', 0.65, 0.469, 0.625, 0.633)),
|
||||||
|
(-87, ('[NPC][Abyssal][Firestorm T1/T2]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||||
|
(-88, ('[NPC][Abyssal][Firestorm T1/T2]Triglavian', 0.534, 0.266, 0.484, 0.366)),
|
||||||
|
(-89, ('[NPC][Abyssal][Firestorm T1/T2]Drifter', 0.422, 0.282, 0.422, 0.422)),
|
||||||
|
(-90, ('[NPC][Abyssal][Firestorm T1/T2]Sleeper', 0.512, 0.402, 0.512, 0.512)),
|
||||||
|
(-91, ('[NPC][Abyssal][Firestorm T1/T2]All', 0.541, 0.365, 0.524, 0.504)),
|
||||||
|
(-92, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drones', 0.461, 0.36, 0.541, 0.443)),
|
||||||
|
(-93, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Overmind', 0.65, 0.391, 0.625, 0.633)),
|
||||||
|
(-94, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||||
|
(-95, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Triglavian', 0.534, 0.161, 0.484, 0.366)),
|
||||||
|
(-96, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drifter', 0.422, 0.196, 0.422, 0.422)),
|
||||||
|
(-97, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Sleeper', 0.512, 0.337, 0.512, 0.512)),
|
||||||
|
(-98, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]All', 0.541, 0.284, 0.524, 0.504)),
|
||||||
|
(-99, ('[NPC][Abyssal][Firestorm T4/T5]Drones', 0.461, 0.305, 0.541, 0.443)),
|
||||||
|
(-100, ('[NPC][Abyssal][Firestorm T4/T5]Overmind', 0.65, 0.323, 0.625, 0.633)),
|
||||||
|
(-101, ('[NPC][Abyssal][Firestorm T4/T5]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||||
|
(-102, ('[NPC][Abyssal][Firestorm T4/T5]Triglavian', 0.534, 0.082, 0.484, 0.366)),
|
||||||
|
(-103, ('[NPC][Abyssal][Firestorm T4/T5]Drifter', 0.422, 0.114, 0.422, 0.422)),
|
||||||
|
(-104, ('[NPC][Abyssal][Firestorm T4/T5]Sleeper', 0.512, 0.276, 0.512, 0.512)),
|
||||||
|
(-105, ('[NPC][Abyssal][Firestorm T4/T5]All', 0.541, 0.214, 0.524, 0.504)),
|
||||||
|
(-106, ('[NPC][Abyssal][Exotic T1/T2]Drones', 0.439, 0.522, 0.417, 0.435)),
|
||||||
|
(-107, ('[NPC][Abyssal][Exotic T1/T2]Overmind', 0.626, 0.576, 0.496, 0.624)),
|
||||||
|
(-108, ('[NPC][Abyssal][Exotic T1/T2]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||||
|
(-109, ('[NPC][Abyssal][Exotic T1/T2]Triglavian', 0.477, 0.401, 0.284, 0.37)),
|
||||||
|
(-110, ('[NPC][Abyssal][Exotic T1/T2]Drifter', 0.403, 0.403, 0.267, 0.403)),
|
||||||
|
(-111, ('[NPC][Abyssal][Exotic T1/T2]Sleeper', 0.435, 0.435, 0.329, 0.435)),
|
||||||
|
(-112, ('[NPC][Abyssal][Exotic T1/T2]All', 0.507, 0.477, 0.373, 0.493)),
|
||||||
|
(-113, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drones', 0.439, 0.522, 0.351, 0.435)),
|
||||||
|
(-114, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Overmind', 0.626, 0.576, 0.419, 0.624)),
|
||||||
|
(-115, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||||
|
(-116, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Triglavian', 0.477, 0.401, 0.176, 0.37)),
|
||||||
|
(-117, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drifter', 0.403, 0.403, 0.184, 0.403)),
|
||||||
|
(-118, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.268, 0.435)),
|
||||||
|
(-119, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]All', 0.507, 0.477, 0.293, 0.493)),
|
||||||
|
(-120, ('[NPC][Abyssal][Exotic T4/T5]Drones', 0.439, 0.522, 0.293, 0.435)),
|
||||||
|
(-121, ('[NPC][Abyssal][Exotic T4/T5]Overmind', 0.626, 0.576, 0.344, 0.624)),
|
||||||
|
(-122, ('[NPC][Abyssal][Exotic T4/T5]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||||
|
(-123, ('[NPC][Abyssal][Exotic T4/T5]Triglavian', 0.477, 0.401, 0.107, 0.37)),
|
||||||
|
(-124, ('[NPC][Abyssal][Exotic T4/T5]Drifter', 0.403, 0.403, 0.107, 0.403)),
|
||||||
|
(-125, ('[NPC][Abyssal][Exotic T4/T5]Sleeper', 0.435, 0.435, 0.215, 0.435)),
|
||||||
|
(-126, ('[NPC][Abyssal][Exotic T4/T5]All', 0.507, 0.477, 0.223, 0.493)),
|
||||||
|
(-127, ('[NPC][Abyssal][Gamma T1/T2]Drones', 0.449, 0.54, 0.549, 0.336)),
|
||||||
|
(-128, ('[NPC][Abyssal][Gamma T1/T2]Overmind', 0.6, 0.557, 0.601, 0.504)),
|
||||||
|
(-129, ('[NPC][Abyssal][Gamma T1/T2]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||||
|
(-130, ('[NPC][Abyssal][Gamma T1/T2]Triglavian', 0.463, 0.392, 0.447, 0.193)),
|
||||||
|
(-131, ('[NPC][Abyssal][Gamma T1/T2]Drifter', 0.428, 0.428, 0.428, 0.287)),
|
||||||
|
(-132, ('[NPC][Abyssal][Gamma T1/T2]Sleeper', 0.435, 0.435, 0.435, 0.329)),
|
||||||
|
(-133, ('[NPC][Abyssal][Gamma T1/T2]All', 0.493, 0.472, 0.5, 0.362)),
|
||||||
|
(-134, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drones', 0.449, 0.54, 0.549, 0.264)),
|
||||||
|
(-135, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Overmind', 0.6, 0.557, 0.601, 0.428)),
|
||||||
|
(-136, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||||
|
(-137, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Triglavian', 0.463, 0.392, 0.447, 0.071)),
|
||||||
|
(-138, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drifter', 0.428, 0.428, 0.428, 0.2)),
|
||||||
|
(-139, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.435, 0.268)),
|
||||||
|
(-140, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]All', 0.493, 0.472, 0.5, 0.28)),
|
||||||
|
(-141, ('[NPC][Abyssal][Gamma T4/T5]Drones', 0.449, 0.54, 0.549, 0.197)),
|
||||||
|
(-142, ('[NPC][Abyssal][Gamma T4/T5]Overmind', 0.6, 0.557, 0.601, 0.356)),
|
||||||
|
(-143, ('[NPC][Abyssal][Gamma T4/T5]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||||
|
(-144, ('[NPC][Abyssal][Gamma T4/T5]Triglavian', 0.463, 0.392, 0.447, 0.029)),
|
||||||
|
(-145, ('[NPC][Abyssal][Gamma T4/T5]Drifter', 0.428, 0.428, 0.428, 0.117)),
|
||||||
|
(-146, ('[NPC][Abyssal][Gamma T4/T5]Sleeper', 0.435, 0.435, 0.435, 0.215)),
|
||||||
|
(-147, ('[NPC][Abyssal][Gamma T4/T5]All', 0.493, 0.472, 0.5, 0.21))])
|
||||||
|
|
||||||
|
|
||||||
class TargetProfile:
|
class TargetProfile:
|
||||||
|
|
||||||
# also determined import/export order - VERY IMPORTANT
|
# also determined import/export order - VERY IMPORTANT
|
||||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
|
||||||
|
_idealTarget = None
|
||||||
|
_builtins = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.builtin = False
|
||||||
self.update(*args, **kwargs)
|
self.update(*args, **kwargs)
|
||||||
|
|
||||||
|
@reconstructor
|
||||||
|
def init(self):
|
||||||
|
self.builtin = False
|
||||||
|
|
||||||
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None):
|
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None):
|
||||||
self.emAmount = emAmount
|
self.emAmount = emAmount
|
||||||
self.thermalAmount = thermalAmount
|
self.thermalAmount = thermalAmount
|
||||||
@@ -44,7 +206,29 @@ class TargetProfile:
|
|||||||
self._signatureRadius = signatureRadius
|
self._signatureRadius = signatureRadius
|
||||||
self._radius = radius
|
self._radius = radius
|
||||||
|
|
||||||
_idealTarget = None
|
@classmethod
|
||||||
|
def getBuiltinList(cls):
|
||||||
|
if cls._builtins is None:
|
||||||
|
cls.__generateBuiltins()
|
||||||
|
return list(cls._builtins.values())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getBuiltinById(cls, id):
|
||||||
|
if cls._builtins is None:
|
||||||
|
cls.__generateBuiltins()
|
||||||
|
return cls._builtins.get(id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __generateBuiltins(cls):
|
||||||
|
cls._builtins = OrderedDict()
|
||||||
|
for id, data in BUILTINS.items():
|
||||||
|
rawName = data[0]
|
||||||
|
data = data[1:]
|
||||||
|
profile = TargetProfile(*data)
|
||||||
|
profile.ID = id
|
||||||
|
profile.rawName = rawName
|
||||||
|
profile.builtin = True
|
||||||
|
cls._builtins[id] = profile
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getIdeal(cls):
|
def getIdeal(cls):
|
||||||
@@ -57,8 +241,9 @@ class TargetProfile:
|
|||||||
maxVelocity=0,
|
maxVelocity=0,
|
||||||
signatureRadius=None,
|
signatureRadius=None,
|
||||||
radius=0)
|
radius=0)
|
||||||
cls._idealTarget.name = 'Ideal Target'
|
cls._idealTarget.rawName = 'Ideal Target'
|
||||||
cls._idealTarget.ID = -1
|
cls._idealTarget.ID = 0
|
||||||
|
cls._idealTarget.builtin = True
|
||||||
return cls._idealTarget
|
return cls._idealTarget
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -77,6 +262,8 @@ class TargetProfile:
|
|||||||
|
|
||||||
@signatureRadius.setter
|
@signatureRadius.setter
|
||||||
def signatureRadius(self, val):
|
def signatureRadius(self, val):
|
||||||
|
if val is not None and math.isinf(val):
|
||||||
|
val = None
|
||||||
self._signatureRadius = val
|
self._signatureRadius = val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -98,7 +285,7 @@ class TargetProfile:
|
|||||||
lookup = {}
|
lookup = {}
|
||||||
current = eos.db.getTargetProfileList()
|
current = eos.db.getTargetProfileList()
|
||||||
for pattern in current:
|
for pattern in current:
|
||||||
lookup[pattern.name] = pattern
|
lookup[pattern.rawName] = pattern
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
try:
|
try:
|
||||||
@@ -106,7 +293,9 @@ class TargetProfile:
|
|||||||
continue
|
continue
|
||||||
line = line.split('#', 1)[0] # allows for comments
|
line = line.split('#', 1)[0] # allows for comments
|
||||||
type, data = line.rsplit('=', 1)
|
type, data = line.rsplit('=', 1)
|
||||||
type, data = type.strip(), data.split(',')
|
type, data = type.strip(), [d.strip() for d in data.split(',')]
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
pyfalog.warning("Data isn't in correct format, continue to next line.")
|
pyfalog.warning("Data isn't in correct format, continue to next line.")
|
||||||
continue
|
continue
|
||||||
@@ -115,26 +304,41 @@ class TargetProfile:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
numPatterns += 1
|
numPatterns += 1
|
||||||
name, data = data[0], data[1:5]
|
name, dataRes, dataMisc = data[0], data[1:5], data[5:8]
|
||||||
fields = {}
|
fields = {}
|
||||||
|
|
||||||
for index, val in enumerate(data):
|
for index, val in enumerate(dataRes):
|
||||||
val = float(val)
|
val = float(val) if val else 0
|
||||||
|
if math.isinf(val):
|
||||||
|
val = 0
|
||||||
try:
|
try:
|
||||||
assert 0 <= val <= 100
|
assert 0 <= val <= 100
|
||||||
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100
|
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
pyfalog.warning("Caught unhandled exception in import patterns.")
|
pyfalog.warning("Caught unhandled exception in import patterns.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if len(fields) == 4: # Avoid possible blank lines
|
if len(dataMisc) == 3:
|
||||||
|
for index, val in enumerate(dataMisc):
|
||||||
|
try:
|
||||||
|
fieldName = ("maxVelocity", "signatureRadius", "radius")[index]
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
|
val = float(val) if val else 0
|
||||||
|
if fieldName != "signatureRadius" and math.isinf(val):
|
||||||
|
val = 0
|
||||||
|
fields[fieldName] = val
|
||||||
|
|
||||||
|
if len(fields) in (4, 7): # Avoid possible blank lines
|
||||||
if name.strip() in lookup:
|
if name.strip() in lookup:
|
||||||
pattern = lookup[name.strip()]
|
pattern = lookup[name.strip()]
|
||||||
pattern.update(**fields)
|
pattern.update(**fields)
|
||||||
eos.db.save(pattern)
|
eos.db.save(pattern)
|
||||||
else:
|
else:
|
||||||
pattern = TargetProfile(**fields)
|
pattern = TargetProfile(**fields)
|
||||||
pattern.name = name.strip()
|
pattern.rawName = name.strip()
|
||||||
eos.db.save(pattern)
|
eos.db.save(pattern)
|
||||||
patterns.append(pattern)
|
patterns.append(pattern)
|
||||||
|
|
||||||
@@ -142,27 +346,60 @@ class TargetProfile:
|
|||||||
|
|
||||||
return patterns, numPatterns
|
return patterns, numPatterns
|
||||||
|
|
||||||
EXPORT_FORMAT = "TargetProfile = %s,%.1f,%.1f,%.1f,%.1f\n"
|
EXPORT_FORMAT = "TargetProfile = %s,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\n"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def exportPatterns(cls, *patterns):
|
def exportPatterns(cls, *patterns):
|
||||||
out = "# Exported from pyfa\n#\n"
|
out = "# Exported from pyfa\n#\n"
|
||||||
out += "# Values are in following format:\n"
|
out += "# Values are in following format:\n"
|
||||||
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %]\n\n"
|
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %],[Max velocity m/s],[Signature radius m],[Radius m]\n\n"
|
||||||
for dp in patterns:
|
for dp in patterns:
|
||||||
out += cls.EXPORT_FORMAT % (
|
out += cls.EXPORT_FORMAT % (
|
||||||
dp.name,
|
dp.rawName,
|
||||||
dp.emAmount * 100,
|
dp.emAmount * 100,
|
||||||
dp.thermalAmount * 100,
|
dp.thermalAmount * 100,
|
||||||
dp.kineticAmount * 100,
|
dp.kineticAmount * 100,
|
||||||
dp.explosiveAmount * 100
|
dp.explosiveAmount * 100,
|
||||||
|
dp.maxVelocity,
|
||||||
|
dp.signatureRadius,
|
||||||
|
dp.radius
|
||||||
)
|
)
|
||||||
|
|
||||||
return out.strip()
|
return out.strip()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.rawName
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullName(self):
|
||||||
|
categories, tail = self.__parseRawName()
|
||||||
|
return '{}{}'.format(''.join('[{}]'.format(c) for c in categories), tail)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def shortName(self):
|
||||||
|
return self.__parseRawName()[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hierarchy(self):
|
||||||
|
return self.__parseRawName()[0]
|
||||||
|
|
||||||
|
def __parseRawName(self):
|
||||||
|
hierarchy = []
|
||||||
|
remainingName = self.rawName.strip() if self.rawName else ''
|
||||||
|
while True:
|
||||||
|
start, end = remainingName.find('['), remainingName.find(']')
|
||||||
|
if start == -1 or end == -1:
|
||||||
|
return hierarchy, remainingName
|
||||||
|
splitter = remainingName.find('|')
|
||||||
|
if splitter != -1 and splitter == start - 1:
|
||||||
|
return hierarchy, remainingName[1:]
|
||||||
|
hierarchy.append(remainingName[start + 1:end])
|
||||||
|
remainingName = remainingName[end + 1:].strip()
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
p = TargetProfile(
|
p = TargetProfile(
|
||||||
self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount,
|
self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount,
|
||||||
self._maxVelocity, self._signatureRadius, self._radius)
|
self._maxVelocity, self._signatureRadius, self._radius)
|
||||||
p.name = "%s copy" % self.name
|
p.rawName = "%s copy" % self.rawName
|
||||||
return p
|
return p
|
||||||
|
|||||||
3
eos/utils/default.py
Normal file
3
eos/utils/default.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class DEFAULT:
|
||||||
|
"""Singleton class to signify default argument value."""
|
||||||
|
pass
|
||||||
39
eos/utils/pyinst_support.py
Normal file
39
eos/utils/pyinst_support.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""
|
||||||
|
Slightly modified version of function taken from here:
|
||||||
|
https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-525221546
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import pkgutil
|
||||||
|
|
||||||
|
|
||||||
|
def iterNamespace(name, path):
|
||||||
|
"""Pyinstaller-compatible namespace iteration.
|
||||||
|
|
||||||
|
Yields the name of all modules found at a given Fully-qualified path.
|
||||||
|
|
||||||
|
To have it running with pyinstaller, it requires to ensure a hook inject the
|
||||||
|
"hidden" modules from your plugins folder inside the executable:
|
||||||
|
|
||||||
|
- if your plugins are under the ``myappname/pluginfolder`` module
|
||||||
|
- create a file ``specs/hook-<myappname.pluginfolder>.py``
|
||||||
|
- content of this file should be:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from PyInstaller.utils.hooks import collect_submodules
|
||||||
|
hiddenimports = collect_submodules('<myappname.pluginfolder>')
|
||||||
|
"""
|
||||||
|
prefix = name + "."
|
||||||
|
for p in pkgutil.iter_modules(path, prefix):
|
||||||
|
yield p[1]
|
||||||
|
|
||||||
|
# special handling when the package is bundled with PyInstaller 3.5
|
||||||
|
# See https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-445787510
|
||||||
|
toc = set()
|
||||||
|
for importer in pkgutil.iter_importers(name.partition(".")[0]):
|
||||||
|
if hasattr(importer, 'toc'):
|
||||||
|
toc |= importer.toc
|
||||||
|
for name in toc:
|
||||||
|
if name.startswith(prefix):
|
||||||
|
yield name
|
||||||
27
eos/utils/round.py
Normal file
27
eos/utils/round.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def roundToPrec(val, prec, nsValue=None):
|
||||||
|
"""
|
||||||
|
nsValue: custom value which should be used to determine normalization shift
|
||||||
|
"""
|
||||||
|
# We're not rounding integers anyway
|
||||||
|
# Also make sure that we do not ask to calculate logarithm of zero
|
||||||
|
if int(val) == val:
|
||||||
|
return int(val)
|
||||||
|
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||||
|
# But we don't want to round integers
|
||||||
|
if roundFactor < 0:
|
||||||
|
roundFactor = 0
|
||||||
|
# Do actual rounding
|
||||||
|
val = round(val, roundFactor)
|
||||||
|
# Make sure numbers with .0 part designating float don't get through
|
||||||
|
if int(val) == val:
|
||||||
|
val = int(val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def roundDec(val, prec):
|
||||||
|
if int(val) == val:
|
||||||
|
return int(val)
|
||||||
|
return round(val, prec)
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from eos.const import SpoolType
|
from eos.const import SpoolType
|
||||||
@@ -36,15 +37,33 @@ def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAm
|
|||||||
"""
|
"""
|
||||||
if not modMaxValue or not modStepValue:
|
if not modMaxValue or not modStepValue:
|
||||||
return 0, 0, 0
|
return 0, 0, 0
|
||||||
if spoolType == SpoolType.SCALE:
|
if spoolType == SpoolType.SPOOL_SCALE:
|
||||||
cycles = int(floatUnerr(spoolAmount * modMaxValue / modStepValue))
|
# Find out at which point of spoolup scale we're on, find out how many cycles
|
||||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
# is enough to reach it and recalculate spoolup value for that amount of cycles
|
||||||
|
cycles = math.ceil(floatUnerr(modMaxValue * spoolAmount / modStepValue))
|
||||||
|
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||||
|
return spoolValue, cycles, cycles * modCycleTime
|
||||||
|
elif spoolType == SpoolType.CYCLE_SCALE:
|
||||||
|
# For cycle scale, find out max amount of cycles and scale against it
|
||||||
|
cycles = round(spoolAmount * math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||||
|
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||||
|
return spoolValue, cycles, cycles * modCycleTime
|
||||||
elif spoolType == SpoolType.TIME:
|
elif spoolType == SpoolType.TIME:
|
||||||
cycles = min(int(floatUnerr(spoolAmount / modCycleTime)), int(floatUnerr(modMaxValue / modStepValue)))
|
cycles = min(
|
||||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
# How many full cycles mod had by passed time
|
||||||
|
math.floor(floatUnerr(spoolAmount / modCycleTime)),
|
||||||
|
# Max amount of cycles
|
||||||
|
math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||||
|
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||||
|
return spoolValue, cycles, cycles * modCycleTime
|
||||||
elif spoolType == SpoolType.CYCLES:
|
elif spoolType == SpoolType.CYCLES:
|
||||||
cycles = min(int(spoolAmount), int(floatUnerr(modMaxValue / modStepValue)))
|
cycles = min(
|
||||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
# Consider full cycles only
|
||||||
|
math.floor(spoolAmount),
|
||||||
|
# Max amount of cycles
|
||||||
|
math.ceil(floatUnerr(modMaxValue / modStepValue)))
|
||||||
|
spoolValue = min(modMaxValue, cycles * modStepValue)
|
||||||
|
return spoolValue, cycles, cycles * modCycleTime
|
||||||
else:
|
else:
|
||||||
return 0, 0, 0
|
return 0, 0, 0
|
||||||
|
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ class DmgTypes:
|
|||||||
# Round for comparison's sake because often damage profiles are
|
# Round for comparison's sake because often damage profiles are
|
||||||
# generated from data which includes float errors
|
# generated from data which includes float errors
|
||||||
return (
|
return (
|
||||||
floatUnerr(self.em) == floatUnerr(other.em) and
|
floatUnerr(self.em) == floatUnerr(other.em) and
|
||||||
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
|
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
|
||||||
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
|
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
|
||||||
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
|
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
|
||||||
floatUnerr(self.total) == floatUnerr(other.total))
|
floatUnerr(self.total) == floatUnerr(other.total))
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return any((
|
return any((
|
||||||
@@ -110,9 +110,19 @@ class DmgTypes:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
spec = ['em', 'thermal', 'kinetic', 'explosive', 'total']
|
spec = DmgTypes.names()
|
||||||
|
spec.append('total')
|
||||||
return makeReprStr(self, spec)
|
return makeReprStr(self, spec)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def names(short=None, postProcessor=None):
|
||||||
|
value = ['em', 'th', 'kin', 'exp'] if short else ['em', 'thermal', 'kinetic', 'explosive']
|
||||||
|
|
||||||
|
if postProcessor:
|
||||||
|
value = [postProcessor(x) for x in value]
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
class RRTypes:
|
class RRTypes:
|
||||||
"""Container for tank data stats."""
|
"""Container for tank data stats."""
|
||||||
@@ -136,10 +146,10 @@ class RRTypes:
|
|||||||
# Round for comparison's sake because often tanking numbers are
|
# Round for comparison's sake because often tanking numbers are
|
||||||
# generated from data which includes float errors
|
# generated from data which includes float errors
|
||||||
return (
|
return (
|
||||||
floatUnerr(self.shield) == floatUnerr(other.shield) and
|
floatUnerr(self.shield) == floatUnerr(other.shield) and
|
||||||
floatUnerr(self.armor) == floatUnerr(other.armor) and
|
floatUnerr(self.armor) == floatUnerr(other.armor) and
|
||||||
floatUnerr(self.hull) == floatUnerr(other.hull) and
|
floatUnerr(self.hull) == floatUnerr(other.hull) and
|
||||||
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
|
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
return any((self.shield, self.armor, self.hull, self.capacitor))
|
return any((self.shield, self.armor, self.hull, self.capacitor))
|
||||||
@@ -191,5 +201,17 @@ class RRTypes:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
spec = ['shield', 'armor', 'hull', 'capacitor']
|
spec = RRTypes.names(False)
|
||||||
return makeReprStr(self, spec)
|
return makeReprStr(self, spec)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def names(ehpOnly=True, postProcessor=None):
|
||||||
|
value = ['shield', 'armor', 'hull']
|
||||||
|
|
||||||
|
if not ehpOnly:
|
||||||
|
value.append('capacitor')
|
||||||
|
|
||||||
|
if postProcessor:
|
||||||
|
value = [postProcessor(x) for x in value]
|
||||||
|
|
||||||
|
return value
|
||||||
|
|||||||
@@ -18,48 +18,20 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
import math
|
from service.settings import GraphSettings
|
||||||
|
|
||||||
|
|
||||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
def checkLockRange(src, distance):
|
||||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
|
||||||
if distance is None:
|
if distance is None:
|
||||||
return 1
|
return True
|
||||||
if srcFalloffRange > 0:
|
if GraphSettings.getInstance().get('ignoreLockRange'):
|
||||||
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
return True
|
||||||
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
return distance <= src.item.maxTargetRange
|
||||||
return 0
|
|
||||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
|
||||||
elif distance <= srcOptimalRange:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
def checkDroneControlRange(src, distance):
|
||||||
# as multipliers arrive in different form) in here to not make actual attribute
|
if distance is None:
|
||||||
# calculations slower than they already are due to extra function calls
|
return True
|
||||||
def calculateMultiplier(multipliers):
|
if GraphSettings.getInstance().get('ignoreDCR'):
|
||||||
"""
|
return True
|
||||||
multipliers: dictionary in format:
|
return distance <= src.item.extraAttributes['droneControlRange']
|
||||||
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
|
|
||||||
"""
|
|
||||||
val = 1
|
|
||||||
for penalizedMultipliers in multipliers.values():
|
|
||||||
# A quick explanation of how this works:
|
|
||||||
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
|
||||||
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
|
||||||
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
|
||||||
# 2: The most significant bonuses take the smallest penalty,
|
|
||||||
# This means we'll have to sort
|
|
||||||
abssort = lambda _val: -abs(_val - 1)
|
|
||||||
l1.sort(key=abssort)
|
|
||||||
l2.sort(key=abssort)
|
|
||||||
# 3: The first module doesn't get penalized at all
|
|
||||||
# Any module after the first takes penalties according to:
|
|
||||||
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
|
||||||
for l in (l1, l2):
|
|
||||||
for i in range(len(l)):
|
|
||||||
bonus = l[i]
|
|
||||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
|
||||||
return val
|
|
||||||
|
|||||||
@@ -26,3 +26,5 @@ from . import fitCapacitor
|
|||||||
from . import fitMobility
|
from . import fitMobility
|
||||||
from . import fitWarpTime
|
from . import fitWarpTime
|
||||||
from . import fitLockTime
|
from . import fitLockTime
|
||||||
|
# Hidden graphs, available via ctrl-alt-g
|
||||||
|
from . import fitEcmBurstScanresDamps
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle'
|
|||||||
|
|
||||||
class YDef:
|
class YDef:
|
||||||
|
|
||||||
def __init__(self, handle, unit, label, selectorLabel=None):
|
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.label = label
|
self.label = label
|
||||||
self._selectorLabel = selectorLabel
|
self._selectorLabel = selectorLabel
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selectorLabel(self):
|
def selectorLabel(self):
|
||||||
@@ -53,12 +54,13 @@ class YDef:
|
|||||||
|
|
||||||
class XDef:
|
class XDef:
|
||||||
|
|
||||||
def __init__(self, handle, unit, label, mainInput, selectorLabel=None):
|
def __init__(self, handle, unit, label, mainInput, selectorLabel=None, hidden=False):
|
||||||
self.handle = handle
|
self.handle = handle
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.label = label
|
self.label = label
|
||||||
self.mainInput = mainInput
|
self.mainInput = mainInput
|
||||||
self._selectorLabel = selectorLabel
|
self._selectorLabel = selectorLabel
|
||||||
|
self.hidden = hidden
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selectorLabel(self):
|
def selectorLabel(self):
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from service.const import GraphCacheCleanupReason
|
|||||||
class FitGraph(metaclass=ABCMeta):
|
class FitGraph(metaclass=ABCMeta):
|
||||||
|
|
||||||
# UI stuff
|
# UI stuff
|
||||||
|
hidden = False
|
||||||
views = []
|
views = []
|
||||||
viewMap = {}
|
viewMap = {}
|
||||||
|
|
||||||
|
|||||||
@@ -21,37 +21,53 @@
|
|||||||
import math
|
import math
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from eos.calc import calculateRangeFactor
|
||||||
from eos.const import FittingHardpoint
|
from eos.const import FittingHardpoint
|
||||||
from eos.utils.float import floatUnerr
|
from eos.utils.float import floatUnerr
|
||||||
from graphs.calc import calculateRangeFactor
|
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||||
from service.attribute import Attribute
|
from service.attribute import Attribute
|
||||||
from service.const import GraphDpsDroneMode
|
from service.const import GraphDpsDroneMode
|
||||||
from service.settings import GraphSettings
|
from service.settings import GraphSettings
|
||||||
|
|
||||||
|
|
||||||
def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
applicationMap = {}
|
applicationMap = {}
|
||||||
for mod in src.item.activeModulesIter():
|
for mod in src.item.activeModulesIter():
|
||||||
if not mod.isDealingDamage():
|
if not mod.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
if "ChainLightning" in mod.item.effects:
|
||||||
applicationMap[mod] = getTurretMult(
|
if inLockRange:
|
||||||
mod=mod,
|
applicationMap[mod] = getVortonMult(
|
||||||
src=src,
|
mod=mod,
|
||||||
tgt=tgt,
|
distance=distance,
|
||||||
atkSpeed=atkSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
atkAngle=atkAngle,
|
tgtSigRadius=tgtSigRadius)
|
||||||
distance=distance,
|
elif mod.hardpoint == FittingHardpoint.TURRET:
|
||||||
tgtSpeed=tgtSpeed,
|
if inLockRange:
|
||||||
tgtAngle=tgtAngle,
|
applicationMap[mod] = getTurretMult(
|
||||||
tgtSigRadius=tgtSigRadius)
|
mod=mod,
|
||||||
|
src=src,
|
||||||
|
tgt=tgt,
|
||||||
|
atkSpeed=atkSpeed,
|
||||||
|
atkAngle=atkAngle,
|
||||||
|
distance=distance,
|
||||||
|
tgtSpeed=tgtSpeed,
|
||||||
|
tgtAngle=tgtAngle,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
else:
|
||||||
|
applicationMap[mod] = 0
|
||||||
elif mod.hardpoint == FittingHardpoint.MISSILE:
|
elif mod.hardpoint == FittingHardpoint.MISSILE:
|
||||||
applicationMap[mod] = getLauncherMult(
|
# FoF missiles can shoot beyond lock range
|
||||||
mod=mod,
|
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||||
src=src,
|
applicationMap[mod] = getLauncherMult(
|
||||||
distance=distance,
|
mod=mod,
|
||||||
tgtSpeed=tgtSpeed,
|
distance=distance,
|
||||||
tgtSigRadius=tgtSigRadius)
|
tgtSpeed=tgtSpeed,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
else:
|
||||||
|
applicationMap[mod] = 0
|
||||||
elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
|
elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
|
||||||
applicationMap[mod] = getSmartbombMult(
|
applicationMap[mod] = getSmartbombMult(
|
||||||
mod=mod,
|
mod=mod,
|
||||||
@@ -64,44 +80,58 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
|||||||
distance=distance,
|
distance=distance,
|
||||||
tgtSigRadius=tgtSigRadius)
|
tgtSigRadius=tgtSigRadius)
|
||||||
elif mod.item.group.name == 'Structure Guided Bomb Launcher':
|
elif mod.item.group.name == 'Structure Guided Bomb Launcher':
|
||||||
applicationMap[mod] = getGuidedBombMult(
|
if inLockRange:
|
||||||
mod=mod,
|
applicationMap[mod] = getGuidedBombMult(
|
||||||
src=src,
|
mod=mod,
|
||||||
distance=distance,
|
src=src,
|
||||||
tgtSigRadius=tgtSigRadius)
|
distance=distance,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
else:
|
||||||
|
applicationMap[mod] = 0
|
||||||
elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
|
elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
|
||||||
applicationMap[mod] = getDoomsdayMult(
|
# Only single-target DDs need locks
|
||||||
mod=mod,
|
if not inLockRange and {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(mod.item.effects):
|
||||||
tgt=tgt,
|
applicationMap[mod] = 0
|
||||||
distance=distance,
|
else:
|
||||||
tgtSigRadius=tgtSigRadius)
|
applicationMap[mod] = getDoomsdayMult(
|
||||||
|
mod=mod,
|
||||||
|
tgt=tgt,
|
||||||
|
distance=distance,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if not drone.isDealingDamage():
|
if not drone.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
applicationMap[drone] = getDroneMult(
|
if inLockRange and inDroneRange:
|
||||||
drone=drone,
|
applicationMap[drone] = getDroneMult(
|
||||||
src=src,
|
drone=drone,
|
||||||
tgt=tgt,
|
src=src,
|
||||||
atkSpeed=atkSpeed,
|
tgt=tgt,
|
||||||
atkAngle=atkAngle,
|
atkSpeed=atkSpeed,
|
||||||
distance=distance,
|
atkAngle=atkAngle,
|
||||||
tgtSpeed=tgtSpeed,
|
distance=distance,
|
||||||
tgtAngle=tgtAngle,
|
tgtSpeed=tgtSpeed,
|
||||||
tgtSigRadius=tgtSigRadius)
|
tgtAngle=tgtAngle,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
else:
|
||||||
|
applicationMap[drone] = 0
|
||||||
for fighter in src.item.activeFightersIter():
|
for fighter in src.item.activeFightersIter():
|
||||||
if not fighter.isDealingDamage():
|
if not fighter.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
for ability in fighter.abilities:
|
for ability in fighter.abilities:
|
||||||
if not ability.dealsDamage or not ability.active:
|
if not ability.dealsDamage or not ability.active:
|
||||||
continue
|
continue
|
||||||
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
# Bomb launching doesn't need locks
|
||||||
fighter=fighter,
|
if inLockRange or ability.effect.name == 'fighterAbilityLaunchBomb':
|
||||||
ability=ability,
|
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
||||||
src=src,
|
fighter=fighter,
|
||||||
tgt=tgt,
|
ability=ability,
|
||||||
distance=distance,
|
src=src,
|
||||||
tgtSpeed=tgtSpeed,
|
tgt=tgt,
|
||||||
tgtSigRadius=tgtSigRadius)
|
distance=distance,
|
||||||
|
tgtSpeed=tgtSpeed,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
else:
|
||||||
|
applicationMap[(fighter, ability.effectID)] = 0
|
||||||
# Ensure consistent results - round off a little to avoid float errors
|
# Ensure consistent results - round off a little to avoid float errors
|
||||||
for k, v in applicationMap.items():
|
for k, v in applicationMap.items():
|
||||||
applicationMap[k] = floatUnerr(v)
|
applicationMap[k] = floatUnerr(v)
|
||||||
@@ -127,19 +157,39 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
|
|||||||
return mult
|
return mult
|
||||||
|
|
||||||
|
|
||||||
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
|
def getVortonMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||||
modRange = mod.maxRange
|
rangeFactor = calculateRangeFactor(
|
||||||
if modRange is None:
|
mod.getModifiedItemAttr('maxRange'),
|
||||||
|
0,
|
||||||
|
distance)
|
||||||
|
applicationFactor = _calcMissileFactor(
|
||||||
|
atkEr=mod.getModifiedItemAttr('aoeCloudSize'),
|
||||||
|
atkEv=mod.getModifiedItemAttr('aoeVelocity'),
|
||||||
|
atkDrf=mod.getModifiedItemAttr('aoeDamageReductionFactor'),
|
||||||
|
tgtSpeed=tgtSpeed,
|
||||||
|
tgtSigRadius=tgtSigRadius)
|
||||||
|
return rangeFactor * applicationFactor
|
||||||
|
|
||||||
|
|
||||||
|
def getLauncherMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||||
|
missileMaxRangeData = mod.missileMaxRangeData
|
||||||
|
if missileMaxRangeData is None:
|
||||||
return 0
|
return 0
|
||||||
if distance is not None and distance + src.getRadius() > modRange:
|
# The ranges already consider ship radius
|
||||||
return 0
|
lowerRange, higherRange, higherChance = missileMaxRangeData
|
||||||
mult = _calcMissileFactor(
|
if distance is None or distance <= lowerRange:
|
||||||
|
distanceFactor = 1
|
||||||
|
elif lowerRange < distance <= higherRange:
|
||||||
|
distanceFactor = higherChance
|
||||||
|
else:
|
||||||
|
distanceFactor = 0
|
||||||
|
applicationFactor = _calcMissileFactor(
|
||||||
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
|
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
|
||||||
atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
|
atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
|
||||||
atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
|
atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
tgtSigRadius=tgtSigRadius)
|
tgtSigRadius=tgtSigRadius)
|
||||||
return mult
|
return distanceFactor * applicationFactor
|
||||||
|
|
||||||
|
|
||||||
def getSmartbombMult(mod, distance):
|
def getSmartbombMult(mod, distance):
|
||||||
@@ -200,7 +250,11 @@ def getGuidedBombMult(mod, src, distance, tgtSigRadius):
|
|||||||
|
|
||||||
|
|
||||||
def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||||
if distance is not None and distance > src.item.extraAttributes['droneControlRange']:
|
if (
|
||||||
|
distance is not None and (
|
||||||
|
(not GraphSettings.getInstance().get('ignoreDCR') and distance > src.item.extraAttributes['droneControlRange']) or
|
||||||
|
(not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange))
|
||||||
|
):
|
||||||
return 0
|
return 0
|
||||||
droneSpeed = drone.getModifiedItemAttr('maxVelocity')
|
droneSpeed = drone.getModifiedItemAttr('maxVelocity')
|
||||||
# Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones
|
# Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones
|
||||||
|
|||||||
@@ -20,37 +20,85 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
|
from eos.calc import calculateRangeFactor
|
||||||
from eos.utils.float import floatUnerr
|
from eos.utils.float import floatUnerr
|
||||||
from graphs.calc import calculateRangeFactor
|
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||||
from service.const import GraphDpsDroneMode
|
from service.const import GraphDpsDroneMode
|
||||||
from service.settings import GraphSettings
|
from service.settings import GraphSettings
|
||||||
|
|
||||||
|
|
||||||
def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
|
def _isRegularScram(mod):
|
||||||
|
if not mod.item:
|
||||||
|
return False
|
||||||
|
if not {'warpScrambleBlockMWDWithNPCEffect', 'structureWarpScrambleBlockMWDWithNPCEffect'}.intersection(mod.item.effects):
|
||||||
|
return False
|
||||||
|
if not mod.getModifiedItemAttr('activationBlockedStrenght', 0):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _isHicScram(mod):
|
||||||
|
if not mod.item:
|
||||||
|
return False
|
||||||
|
if 'warpDisruptSphere' not in mod.item.effects:
|
||||||
|
return False
|
||||||
|
if not mod.charge:
|
||||||
|
return False
|
||||||
|
if 'shipModuleFocusedWarpScramblingScript' not in mod.charge.effects:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def getScramRange(src):
|
||||||
|
scramRange = None
|
||||||
|
for mod in src.item.activeModulesIter():
|
||||||
|
if _isRegularScram(mod) or _isHicScram(mod):
|
||||||
|
scramRange = max(scramRange or 0, mod.maxRange or 0)
|
||||||
|
return scramRange
|
||||||
|
|
||||||
|
|
||||||
|
def getScrammables(tgt):
|
||||||
|
scrammables = []
|
||||||
|
if tgt.isFit:
|
||||||
|
for mod in tgt.item.activeModulesIter():
|
||||||
|
if not mod.item:
|
||||||
|
continue
|
||||||
|
if {'moduleBonusMicrowarpdrive', 'microJumpDrive', 'microJumpPortalDrive'}.intersection(mod.item.effects):
|
||||||
|
scrammables.append(mod)
|
||||||
|
return scrammables
|
||||||
|
|
||||||
|
|
||||||
|
def getTackledSpeed(src, tgt, currentUntackledSpeed, srcScramRange, tgtScrammables, webMods, webDrones, webFighters, distance):
|
||||||
# Can slow down non-immune ships and target profiles
|
# Can slow down non-immune ships and target profiles
|
||||||
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
||||||
return currentUnwebbedSpeed
|
return currentUntackledSpeed
|
||||||
maxUnwebbedSpeed = tgt.getMaxVelocity()
|
maxUntackledSpeed = tgt.getMaxVelocity()
|
||||||
# What's immobile cannot be slowed
|
# What's immobile cannot be slowed
|
||||||
if maxUnwebbedSpeed == 0:
|
if maxUntackledSpeed == 0:
|
||||||
return maxUnwebbedSpeed
|
return maxUntackledSpeed
|
||||||
speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
|
speedRatio = currentUntackledSpeed / maxUntackledSpeed
|
||||||
|
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||||
|
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||||
|
tgtScrammables = ()
|
||||||
appliedMultipliers = {}
|
appliedMultipliers = {}
|
||||||
# Modules first, they are applied always the same way
|
# Modules first, they are always applied the same way
|
||||||
for wData in webMods:
|
if inLockRange:
|
||||||
appliedBoost = wData.boost * calculateRangeFactor(
|
for wData in webMods:
|
||||||
srcOptimalRange=wData.optimal,
|
appliedBoost = wData.boost * calculateRangeFactor(
|
||||||
srcFalloffRange=wData.falloff,
|
srcOptimalRange=wData.optimal,
|
||||||
distance=distance)
|
srcFalloffRange=wData.falloff,
|
||||||
if appliedBoost:
|
distance=distance)
|
||||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
if appliedBoost:
|
||||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||||
|
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||||
# Drones and fighters
|
# Drones and fighters
|
||||||
mobileWebs = []
|
mobileWebs = []
|
||||||
mobileWebs.extend(webFighters)
|
if inLockRange:
|
||||||
# Drones have range limit
|
mobileWebs.extend(webFighters)
|
||||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
if inLockRange and inDroneRange:
|
||||||
mobileWebs.extend(webDrones)
|
mobileWebs.extend(webDrones)
|
||||||
atkRadius = src.getRadius()
|
atkRadius = src.getRadius()
|
||||||
# As mobile webs either follow the target or stick to the attacking ship,
|
# As mobile webs either follow the target or stick to the attacking ship,
|
||||||
@@ -60,8 +108,8 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
|||||||
for mwData in longEnoughMws:
|
for mwData in longEnoughMws:
|
||||||
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
|
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
|
||||||
mobileWebs.remove(mwData)
|
mobileWebs.remove(mwData)
|
||||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||||
# Apply remaining webs, from fastest to slowest
|
# Apply remaining webs, from fastest to slowest
|
||||||
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
||||||
while mobileWebs:
|
while mobileWebs:
|
||||||
@@ -70,7 +118,7 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
|||||||
fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
|
fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
|
||||||
for mwData in fastestMws:
|
for mwData in fastestMws:
|
||||||
# Faster than target or set to follow it - apply full slowdown
|
# Faster than target or set to follow it - apply full slowdown
|
||||||
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
|
if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentTackledSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
|
||||||
appliedMwBoost = mwData.boost
|
appliedMwBoost = mwData.boost
|
||||||
# Otherwise project from the center of the ship
|
# Otherwise project from the center of the ship
|
||||||
else:
|
else:
|
||||||
@@ -84,31 +132,37 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
|||||||
distance=rangeFactorDistance)
|
distance=rangeFactorDistance)
|
||||||
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
|
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
|
||||||
mobileWebs.remove(mwData)
|
mobileWebs.remove(mwData)
|
||||||
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
|
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||||
currentWebbedSpeed = maxWebbedSpeed * speedRatio
|
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||||
# Ensure consistent results - round off a little to avoid float errors
|
# Ensure consistent results - round off a little to avoid float errors
|
||||||
return floatUnerr(currentWebbedSpeed)
|
return floatUnerr(currentTackledSpeed)
|
||||||
|
|
||||||
|
|
||||||
def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
def getSigRadiusMult(src, tgt, tgtSpeed, srcScramRange, tgtScrammables, tpMods, tpDrones, tpFighters, distance):
|
||||||
# Can blow non-immune ships and target profiles
|
# Can blow non-immune ships and target profiles
|
||||||
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
||||||
return 1
|
return 1
|
||||||
untpedSig = tgt.getSigRadius()
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
# Modules
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
|
initSig = tgt.getSigRadius()
|
||||||
|
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||||
|
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||||
|
tgtScrammables = ()
|
||||||
|
# TPing modules
|
||||||
appliedMultipliers = {}
|
appliedMultipliers = {}
|
||||||
for tpData in tpMods:
|
if inLockRange:
|
||||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
for tpData in tpMods:
|
||||||
srcOptimalRange=tpData.optimal,
|
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||||
srcFalloffRange=tpData.falloff,
|
srcOptimalRange=tpData.optimal,
|
||||||
distance=distance)
|
srcFalloffRange=tpData.falloff,
|
||||||
if appliedBoost:
|
distance=distance)
|
||||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
if appliedBoost:
|
||||||
# Drones and fighters
|
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||||
|
# TPing drones
|
||||||
mobileTps = []
|
mobileTps = []
|
||||||
mobileTps.extend(tpFighters)
|
if inLockRange:
|
||||||
# Drones have range limit
|
mobileTps.extend(tpFighters)
|
||||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
if inLockRange and inDroneRange:
|
||||||
mobileTps.extend(tpDrones)
|
mobileTps.extend(tpDrones)
|
||||||
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
||||||
atkRadius = src.getRadius()
|
atkRadius = src.getRadius()
|
||||||
@@ -127,9 +181,9 @@ def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
|||||||
srcFalloffRange=mtpData.falloff,
|
srcFalloffRange=mtpData.falloff,
|
||||||
distance=rangeFactorDistance)
|
distance=rangeFactorDistance)
|
||||||
appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
|
appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
|
||||||
tpedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers)
|
modifiedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||||
if tpedSig == math.inf and untpedSig == math.inf:
|
if modifiedSig == math.inf and initSig == math.inf:
|
||||||
return 1
|
return 1
|
||||||
mult = tpedSig / untpedSig
|
mult = modifiedSig / initSig
|
||||||
# Ensure consistent results - round off a little to avoid float errors
|
# Ensure consistent results - round off a little to avoid float errors
|
||||||
return floatUnerr(mult)
|
return floatUnerr(mult)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from eos.utils.stats import DmgTypes
|
|||||||
from graphs.data.base import PointGetter, SmoothPointGetter
|
from graphs.data.base import PointGetter, SmoothPointGetter
|
||||||
from service.settings import GraphSettings
|
from service.settings import GraphSettings
|
||||||
from .calc.application import getApplicationPerKey
|
from .calc.application import getApplicationPerKey
|
||||||
from .calc.projected import getTpMult, getWebbedSpeed
|
from .calc.projected import getScramRange, getScrammables, getTackledSpeed, getSigRadiusMult
|
||||||
|
|
||||||
|
|
||||||
def applyDamage(dmgMap, applicationMap, tgtResists):
|
def applyDamage(dmgMap, applicationMap, tgtResists):
|
||||||
@@ -54,7 +54,7 @@ class YDpsMixin:
|
|||||||
for mod in src.item.activeModulesIter():
|
for mod in src.item.activeModulesIter():
|
||||||
if not mod.isDealingDamage():
|
if not mod.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
|
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if not drone.isDealingDamage():
|
if not drone.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
@@ -88,7 +88,7 @@ class YVolleyMixin:
|
|||||||
for mod in src.item.activeModulesIter():
|
for mod in src.item.activeModulesIter():
|
||||||
if not mod.isDealingDamage():
|
if not mod.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
|
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if not drone.isDealingDamage():
|
if not drone.isDealingDamage():
|
||||||
continue
|
continue
|
||||||
@@ -138,8 +138,11 @@ class XDistanceMixin(SmoothPointGetter):
|
|||||||
# Prepare time cache here because we need to do it only once,
|
# Prepare time cache here because we need to do it only once,
|
||||||
# and this function is called once per point info fetch
|
# and this function is called once per point info fetch
|
||||||
self._prepareTimeCache(src=src, maxTime=miscParams['time'])
|
self._prepareTimeCache(src=src, maxTime=miscParams['time'])
|
||||||
|
applyProjected = GraphSettings.getInstance().get('applyProjected')
|
||||||
return {
|
return {
|
||||||
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
|
'applyProjected': applyProjected,
|
||||||
|
'srcScramRange': getScramRange(src=src) if applyProjected else None,
|
||||||
|
'tgtScrammables': getScrammables(tgt=tgt) if applyProjected else (),
|
||||||
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
|
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
|
||||||
'tgtResists': tgt.getResists()}
|
'tgtResists': tgt.getResists()}
|
||||||
|
|
||||||
@@ -151,18 +154,22 @@ class XDistanceMixin(SmoothPointGetter):
|
|||||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||||
tgtSpeed = getWebbedSpeed(
|
tgtSpeed = getTackledSpeed(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
currentUnwebbedSpeed=tgtSpeed,
|
currentUntackledSpeed=tgtSpeed,
|
||||||
|
srcScramRange=commonData['srcScramRange'],
|
||||||
|
tgtScrammables=commonData['tgtScrammables'],
|
||||||
webMods=webMods,
|
webMods=webMods,
|
||||||
webDrones=webDrones,
|
webDrones=webDrones,
|
||||||
webFighters=webFighters,
|
webFighters=webFighters,
|
||||||
distance=distance)
|
distance=distance)
|
||||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
|
srcScramRange=commonData['srcScramRange'],
|
||||||
|
tgtScrammables=commonData['tgtScrammables'],
|
||||||
tpMods=tpMods,
|
tpMods=tpMods,
|
||||||
tpDrones=tpDrones,
|
tpDrones=tpDrones,
|
||||||
tpFighters=tpFighters,
|
tpFighters=tpFighters,
|
||||||
@@ -189,21 +196,27 @@ class XTimeMixin(PointGetter):
|
|||||||
tgtSpeed = miscParams['tgtSpeed']
|
tgtSpeed = miscParams['tgtSpeed']
|
||||||
tgtSigRadius = tgt.getSigRadius()
|
tgtSigRadius = tgt.getSigRadius()
|
||||||
if GraphSettings.getInstance().get('applyProjected'):
|
if GraphSettings.getInstance().get('applyProjected'):
|
||||||
|
srcScramRange = getScramRange(src=src)
|
||||||
|
tgtScrammables = getScrammables(tgt=tgt)
|
||||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||||
tgtSpeed = getWebbedSpeed(
|
tgtSpeed = getTackledSpeed(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
currentUnwebbedSpeed=tgtSpeed,
|
currentUntackledSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
webMods=webMods,
|
webMods=webMods,
|
||||||
webDrones=webDrones,
|
webDrones=webDrones,
|
||||||
webFighters=webFighters,
|
webFighters=webFighters,
|
||||||
distance=miscParams['distance'])
|
distance=miscParams['distance'])
|
||||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
tpMods=tpMods,
|
tpMods=tpMods,
|
||||||
tpDrones=tpDrones,
|
tpDrones=tpDrones,
|
||||||
tpFighters=tpFighters,
|
tpFighters=tpFighters,
|
||||||
@@ -303,21 +316,27 @@ class XTgtSpeedMixin(SmoothPointGetter):
|
|||||||
tgtSpeed = x
|
tgtSpeed = x
|
||||||
tgtSigRadius = tgt.getSigRadius()
|
tgtSigRadius = tgt.getSigRadius()
|
||||||
if commonData['applyProjected']:
|
if commonData['applyProjected']:
|
||||||
|
srcScramRange = getScramRange(src=src)
|
||||||
|
tgtScrammables = getScrammables(tgt=tgt)
|
||||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||||
tgtSpeed = getWebbedSpeed(
|
tgtSpeed = getTackledSpeed(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
currentUnwebbedSpeed=tgtSpeed,
|
currentUntackledSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
webMods=webMods,
|
webMods=webMods,
|
||||||
webDrones=webDrones,
|
webDrones=webDrones,
|
||||||
webFighters=webFighters,
|
webFighters=webFighters,
|
||||||
distance=miscParams['distance'])
|
distance=miscParams['distance'])
|
||||||
tgtSigRadius = tgtSigRadius * getTpMult(
|
tgtSigRadius = tgtSigRadius * getSigRadiusMult(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
tpMods=tpMods,
|
tpMods=tpMods,
|
||||||
tpDrones=tpDrones,
|
tpDrones=tpDrones,
|
||||||
tpFighters=tpFighters,
|
tpFighters=tpFighters,
|
||||||
@@ -347,21 +366,27 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
|
|||||||
tgtSpeed = miscParams['tgtSpeed']
|
tgtSpeed = miscParams['tgtSpeed']
|
||||||
tgtSigMult = 1
|
tgtSigMult = 1
|
||||||
if GraphSettings.getInstance().get('applyProjected'):
|
if GraphSettings.getInstance().get('applyProjected'):
|
||||||
|
srcScramRange = getScramRange(src=src)
|
||||||
|
tgtScrammables = getScrammables(tgt=tgt)
|
||||||
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
|
||||||
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
|
||||||
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
|
||||||
tgtSpeed = getWebbedSpeed(
|
tgtSpeed = getTackledSpeed(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
currentUnwebbedSpeed=tgtSpeed,
|
currentUntackledSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
webMods=webMods,
|
webMods=webMods,
|
||||||
webDrones=webDrones,
|
webDrones=webDrones,
|
||||||
webFighters=webFighters,
|
webFighters=webFighters,
|
||||||
distance=miscParams['distance'])
|
distance=miscParams['distance'])
|
||||||
tgtSigMult = getTpMult(
|
tgtSigMult = getSigRadiusMult(
|
||||||
src=src,
|
src=src,
|
||||||
tgt=tgt,
|
tgt=tgt,
|
||||||
tgtSpeed=tgtSpeed,
|
tgtSpeed=tgtSpeed,
|
||||||
|
srcScramRange=srcScramRange,
|
||||||
|
tgtScrammables=tgtScrammables,
|
||||||
tpMods=tpMods,
|
tpMods=tpMods,
|
||||||
tpDrones=tpDrones,
|
tpDrones=tpDrones,
|
||||||
tpFighters=tpFighters,
|
tpFighters=tpFighters,
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ from .getter import (
|
|||||||
|
|
||||||
class FitDamageStatsGraph(FitGraph):
|
class FitDamageStatsGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._timeCache = TimeCache()
|
self._timeCache = TimeCache()
|
||||||
self._projectedCache = ProjectedDataCache()
|
self._projectedCache = ProjectedDataCache()
|
||||||
|
|
||||||
|
|||||||
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Copyright (C) 2010 Diego Duclos
|
||||||
|
#
|
||||||
|
# This file is part of pyfa.
|
||||||
|
#
|
||||||
|
# pyfa is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pyfa is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
from .graph import FitEcmBurstScanresDampsGraph
|
||||||
|
|
||||||
|
|
||||||
|
FitEcmBurstScanresDampsGraph.register()
|
||||||
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Copyright (C) 2010 Diego Duclos
|
||||||
|
#
|
||||||
|
# This file is part of pyfa.
|
||||||
|
#
|
||||||
|
# pyfa is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pyfa is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
from eos.calc import calculateLockTime
|
||||||
|
from graphs.data.base import SmoothPointGetter
|
||||||
|
|
||||||
|
|
||||||
|
ECM_BURST_DURATION = 30
|
||||||
|
DRONE_LOCK_TIME = 2
|
||||||
|
|
||||||
|
|
||||||
|
class TgtScanRes2TgtLockTimeGetter(SmoothPointGetter):
|
||||||
|
|
||||||
|
def _getCommonData(self, miscParams, src, tgt):
|
||||||
|
if miscParams['applyDamps']:
|
||||||
|
tgtScanResMult = src.item.getDampMultScanRes()
|
||||||
|
else:
|
||||||
|
tgtScanResMult = 1
|
||||||
|
return {
|
||||||
|
'tgtScanResMult': tgtScanResMult,
|
||||||
|
'sigRadius': src.item.ship.getModifiedItemAttr('signatureRadius')}
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
scanRes = x
|
||||||
|
time = calculateLockTime(
|
||||||
|
srcScanRes=scanRes * commonData['tgtScanResMult'],
|
||||||
|
tgtSigRadius=commonData['sigRadius'])
|
||||||
|
return time
|
||||||
|
|
||||||
|
|
||||||
|
class TgtScanRes2TgtLockUptimeGetter(TgtScanRes2TgtLockTimeGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, *args, **kwargs):
|
||||||
|
# Assuming you ECM burst every 30 seconds, find out how long you
|
||||||
|
# will be locked before you burst another time
|
||||||
|
lockTime = super()._calculatePoint(*args, **kwargs)
|
||||||
|
lockedTime = max(0, ECM_BURST_DURATION - lockTime)
|
||||||
|
return lockedTime
|
||||||
|
|
||||||
|
|
||||||
|
class SrcDmgBaseGetter(SmoothPointGetter):
|
||||||
|
|
||||||
|
def _getCommonData(self, miscParams, src, tgt):
|
||||||
|
if miscParams['applyDamps']:
|
||||||
|
tgtScanResMult = src.item.getDampMultScanRes()
|
||||||
|
else:
|
||||||
|
tgtScanResMult = 1
|
||||||
|
return {
|
||||||
|
'tgtScanResMult': tgtScanResMult,
|
||||||
|
'srcSigRadius': src.item.ship.getModifiedItemAttr('signatureRadius'),
|
||||||
|
'srcEhp': sum(src.item.ehp.values()),
|
||||||
|
'srcDpsWeapon': src.item.getWeaponDps().total,
|
||||||
|
'srcDpsDrone': src.item.getDroneDps().total if miscParams['applyDrones'] else 0}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calculateInflictedDamage(srcSigRadius, srcWeaponDps, srcDroneDps, srcEhp, tgtScanRes, tgtDps, uptimeAdjustment, uptimeAmountLimit):
|
||||||
|
lockTime = calculateLockTime(srcScanRes=tgtScanRes, tgtSigRadius=srcSigRadius)
|
||||||
|
lockUptime = max(0, ECM_BURST_DURATION - lockTime - uptimeAdjustment)
|
||||||
|
lockDowntime = ECM_BURST_DURATION - lockUptime
|
||||||
|
inflictedDmg = 0
|
||||||
|
remainingEhp = srcEhp
|
||||||
|
for i in range(int(uptimeAmountLimit)):
|
||||||
|
timeAliveUnderFire = min(lockUptime, remainingEhp / tgtDps)
|
||||||
|
timeAlive = lockDowntime + timeAliveUnderFire
|
||||||
|
remainingEhp -= lockUptime * tgtDps
|
||||||
|
inflictedDmg += timeAlive * srcWeaponDps
|
||||||
|
inflictedDmg += max(0, timeAlive - DRONE_LOCK_TIME - 1) * srcDroneDps
|
||||||
|
if remainingEhp <= 0:
|
||||||
|
break
|
||||||
|
return inflictedDmg
|
||||||
|
|
||||||
|
|
||||||
|
class TgtScanRes2SrcDmgGetter(SrcDmgBaseGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
damage = self._calculateInflictedDamage(
|
||||||
|
srcSigRadius=commonData['srcSigRadius'],
|
||||||
|
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||||
|
srcDroneDps=commonData['srcDpsDrone'],
|
||||||
|
srcEhp=commonData['srcEhp'],
|
||||||
|
tgtScanRes=x * commonData['tgtScanResMult'],
|
||||||
|
tgtDps=miscParams['tgtDps'],
|
||||||
|
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||||
|
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||||
|
return damage
|
||||||
|
|
||||||
|
|
||||||
|
class TgtDps2SrcDmgGetter(SrcDmgBaseGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
damage = self._calculateInflictedDamage(
|
||||||
|
srcSigRadius=commonData['srcSigRadius'],
|
||||||
|
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||||
|
srcDroneDps=commonData['srcDpsDrone'],
|
||||||
|
srcEhp=commonData['srcEhp'],
|
||||||
|
tgtScanRes=miscParams['tgtScanRes'] * commonData['tgtScanResMult'],
|
||||||
|
tgtDps=x,
|
||||||
|
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||||
|
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||||
|
return damage
|
||||||
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Copyright (C) 2010 Diego Duclos
|
||||||
|
#
|
||||||
|
# This file is part of pyfa.
|
||||||
|
#
|
||||||
|
# pyfa is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# pyfa is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Disclaimer by kadesh: this graph was made to analyze my ECM burst + damp frig
|
||||||
|
concept. I do not think it is useful for regular player, so it is disabled.
|
||||||
|
Enable by setting config.experimentalFeatures = True.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from graphs.data.base import FitGraph, XDef, YDef, Input, InputCheckbox
|
||||||
|
from .getter import (
|
||||||
|
TgtScanRes2TgtLockTimeGetter, TgtScanRes2TgtLockUptimeGetter,
|
||||||
|
TgtScanRes2SrcDmgGetter, TgtDps2SrcDmgGetter)
|
||||||
|
|
||||||
|
|
||||||
|
class FitEcmBurstScanresDampsGraph(FitGraph):
|
||||||
|
|
||||||
|
# UI stuff
|
||||||
|
hidden = True
|
||||||
|
internalName = 'ecmBurstScanresDamps'
|
||||||
|
name = 'ECM Burst + Scanres Damps'
|
||||||
|
xDefs = [
|
||||||
|
XDef(handle='tgtDps', unit=None, label='Enemy DPS', mainInput=('tgtDps', None)),
|
||||||
|
XDef(handle='tgtScanRes', unit='mm', label='Enemy scanres', mainInput=('tgtScanRes', 'mm'))]
|
||||||
|
yDefs = [
|
||||||
|
YDef(handle='srcDmg', unit=None, label='Damage inflicted'),
|
||||||
|
YDef(handle='tgtLockTime', unit='s', label='Lock time'),
|
||||||
|
YDef(handle='tgtLockUptime', unit='s', label='Lock uptime')]
|
||||||
|
inputs = [
|
||||||
|
Input(handle='tgtScanRes', unit='mm', label='Enemy scanres', iconID=74, defaultValue=700, defaultRange=(100, 1000)),
|
||||||
|
Input(handle='tgtDps', unit=None, label='Enemy DPS', iconID=1432, defaultValue=200, defaultRange=(100, 600)),
|
||||||
|
Input(handle='uptimeAdj', unit='s', label='Uptime adjustment', iconID=1392, defaultValue=1, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))]),
|
||||||
|
Input(handle='uptimeAmtLimit', unit=None, label='Max amount of uptimes', iconID=1397, defaultValue=3, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))])]
|
||||||
|
checkboxes = [
|
||||||
|
InputCheckbox(handle='applyDamps', label='Apply sensor dampeners', defaultValue=True),
|
||||||
|
InputCheckbox(handle='applyDrones', label='Use drones', defaultValue=True, conditions=[(None, ('srcDmg', None))])]
|
||||||
|
srcExtraCols = ('SigRadius', 'Damp ScanRes')
|
||||||
|
|
||||||
|
# Calculation stuff
|
||||||
|
_limiters = {'tgtScanRes': lambda src, tgt: (1, math.inf)}
|
||||||
|
_getters = {
|
||||||
|
('tgtScanRes', 'tgtLockTime'): TgtScanRes2TgtLockTimeGetter,
|
||||||
|
('tgtScanRes', 'tgtLockUptime'): TgtScanRes2TgtLockUptimeGetter,
|
||||||
|
('tgtScanRes', 'srcDmg'): TgtScanRes2SrcDmgGetter,
|
||||||
|
('tgtDps', 'srcDmg'): TgtDps2SrcDmgGetter}
|
||||||
@@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from graphs.calc import calculateMultiplier, calculateRangeFactor
|
from eos.calc import calculateMultiplier, calculateRangeFactor
|
||||||
|
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||||
from graphs.data.base import SmoothPointGetter
|
from graphs.data.base import SmoothPointGetter
|
||||||
|
|
||||||
|
|
||||||
@@ -37,33 +38,37 @@ class Distance2NeutingStrGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
neuts.append((
|
neuts.append((
|
||||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0))
|
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||||
if 'energyNosferatuFalloff' in mod.item.effects and mod.getModifiedItemAttr('nosOverride'):
|
if 'energyNosferatuFalloff' in mod.item.effects and mod.getModifiedItemAttr('nosOverride'):
|
||||||
neuts.append((
|
neuts.append((
|
||||||
mod.getModifiedItemAttr('powerTransferAmount') / self.__getDuration(mod) * resonance,
|
mod.getModifiedItemAttr('powerTransferAmount') / self.__getDuration(mod) * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0))
|
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||||
if 'doomsdayAOENeut' in mod.item.effects:
|
if 'doomsdayAOENeut' in mod.item.effects:
|
||||||
neuts.append((
|
neuts.append((
|
||||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0))
|
mod.falloff or 0, False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'entityEnergyNeutralizerFalloff' in drone.item.effects:
|
if 'entityEnergyNeutralizerFalloff' in drone.item.effects:
|
||||||
neuts.extend(drone.amountActive * ((
|
neuts.extend(drone.amountActive * ((
|
||||||
drone.getModifiedItemAttr('energyNeutralizerAmount') / (drone.getModifiedItemAttr('energyNeutralizerDuration') / 1000) * resonance,
|
drone.getModifiedItemAttr('energyNeutralizerAmount') / (drone.getModifiedItemAttr('energyNeutralizerDuration') / 1000) * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0),))
|
math.inf, 0, True, True),))
|
||||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||||
if ability.effect.name == 'fighterAbilityEnergyNeutralizer':
|
if ability.effect.name == 'fighterAbilityEnergyNeutralizer':
|
||||||
nps = fighter.getModifiedItemAttr('fighterAbilityEnergyNeutralizerAmount') / (ability.cycleTime / 1000)
|
nps = fighter.getModifiedItemAttr('fighterAbilityEnergyNeutralizerAmount') / (ability.cycleTime / 1000)
|
||||||
neuts.append((
|
neuts.append((
|
||||||
nps * fighter.amount * resonance,
|
nps * fighter.amount * resonance,
|
||||||
math.inf, 0))
|
math.inf, 0, True, False))
|
||||||
return {'neuts': neuts}
|
return {'neuts': neuts}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
combinedStr = 0
|
combinedStr = 0
|
||||||
for strength, optimal, falloff in commonData['neuts']:
|
for strength, optimal, falloff, needsLock, needsDcr in commonData['neuts']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
return combinedStr
|
return combinedStr
|
||||||
|
|
||||||
@@ -84,28 +89,32 @@ class Distance2WebbingStrGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
webs.append((
|
webs.append((
|
||||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||||
if 'doomsdayAOEWeb' in mod.item.effects:
|
if 'doomsdayAOEWeb' in mod.item.effects:
|
||||||
webs.append((
|
webs.append((
|
||||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0, 'default'))
|
mod.falloff or 0, 'default', False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'remoteWebifierEntity' in drone.item.effects:
|
if 'remoteWebifierEntity' in drone.item.effects:
|
||||||
webs.extend(drone.amountActive * ((
|
webs.extend(drone.amountActive * ((
|
||||||
drone.getModifiedItemAttr('speedFactor') * resonance,
|
drone.getModifiedItemAttr('speedFactor') * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
math.inf, 0, 'default', True, True),))
|
||||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||||
webs.append((
|
webs.append((
|
||||||
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amount * resonance,
|
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amount * resonance,
|
||||||
math.inf, 0, 'default'))
|
math.inf, 0, 'default', True, False))
|
||||||
return {'webs': webs}
|
return {'webs': webs}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
strMults = {}
|
strMults = {}
|
||||||
for strength, optimal, falloff, stackingGroup in commonData['webs']:
|
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['webs']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||||
strMult = calculateMultiplier(strMults)
|
strMult = calculateMultiplier(strMults)
|
||||||
@@ -129,28 +138,32 @@ class Distance2EcmStrMaxGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
ecms.append((
|
ecms.append((
|
||||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0))
|
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||||
if 'doomsdayAOEECM' in mod.item.effects:
|
if 'doomsdayAOEECM' in mod.item.effects:
|
||||||
ecms.append((
|
ecms.append((
|
||||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0))
|
mod.falloff or 0, False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'entityECMFalloff' in drone.item.effects:
|
if 'entityECMFalloff' in drone.item.effects:
|
||||||
ecms.extend(drone.amountActive * ((
|
ecms.extend(drone.amountActive * ((
|
||||||
max(drone.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
max(drone.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0),))
|
math.inf, 0, True, True),))
|
||||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||||
if ability.effect.name == 'fighterAbilityECM':
|
if ability.effect.name == 'fighterAbilityECM':
|
||||||
ecms.append((
|
ecms.append((
|
||||||
max(fighter.getModifiedItemAttr(a) for a in self.ECM_ATTRS_FIGHTERS) * fighter.amount * resonance,
|
max(fighter.getModifiedItemAttr(a) for a in self.ECM_ATTRS_FIGHTERS) * fighter.amount * resonance,
|
||||||
math.inf, 0))
|
math.inf, 0, True, False))
|
||||||
return {'ecms': ecms}
|
return {'ecms': ecms}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
combinedStr = 0
|
combinedStr = 0
|
||||||
for strength, optimal, falloff in commonData['ecms']:
|
for strength, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
return combinedStr
|
return combinedStr
|
||||||
|
|
||||||
@@ -168,23 +181,27 @@ class Distance2DampStrLockRangeGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
damps.append((
|
damps.append((
|
||||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||||
if 'doomsdayAOEDamp' in mod.item.effects:
|
if 'doomsdayAOEDamp' in mod.item.effects:
|
||||||
damps.append((
|
damps.append((
|
||||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0, 'default'))
|
mod.falloff or 0, 'default', False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'remoteSensorDampEntity' in drone.item.effects:
|
if 'remoteSensorDampEntity' in drone.item.effects:
|
||||||
damps.extend(drone.amountActive * ((
|
damps.extend(drone.amountActive * ((
|
||||||
drone.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
drone.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
math.inf, 0, 'default', True, True),))
|
||||||
return {'damps': damps}
|
return {'damps': damps}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
strMults = {}
|
strMults = {}
|
||||||
for strength, optimal, falloff, stackingGroup in commonData['damps']:
|
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['damps']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||||
strMult = calculateMultiplier(strMults)
|
strMult = calculateMultiplier(strMults)
|
||||||
@@ -205,23 +222,27 @@ class Distance2TdStrOptimalGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
tds.append((
|
tds.append((
|
||||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||||
if 'doomsdayAOETrack' in mod.item.effects:
|
if 'doomsdayAOETrack' in mod.item.effects:
|
||||||
tds.append((
|
tds.append((
|
||||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0, 'default'))
|
mod.falloff or 0, 'default', False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'npcEntityWeaponDisruptor' in drone.item.effects:
|
if 'npcEntityWeaponDisruptor' in drone.item.effects:
|
||||||
tds.extend(drone.amountActive * ((
|
tds.extend(drone.amountActive * ((
|
||||||
drone.getModifiedItemAttr('maxRangeBonus') * resonance,
|
drone.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
math.inf, 0, 'default', True, True),))
|
||||||
return {'tds': tds}
|
return {'tds': tds}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
strMults = {}
|
strMults = {}
|
||||||
for strength, optimal, falloff, stackingGroup in commonData['tds']:
|
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tds']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||||
strMult = calculateMultiplier(strMults)
|
strMult = calculateMultiplier(strMults)
|
||||||
@@ -243,20 +264,24 @@ class Distance2GdStrRangeGetter(SmoothPointGetter):
|
|||||||
gds.append((
|
gds.append((
|
||||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||||
if 'doomsdayAOETrack' in mod.item.effects:
|
if 'doomsdayAOETrack' in mod.item.effects:
|
||||||
gds.append((
|
gds.append((
|
||||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0, 'default'))
|
mod.falloff or 0, 'default', False, False))
|
||||||
return {'gds': gds}
|
return {'gds': gds}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
velocityStrMults = {}
|
velocityStrMults = {}
|
||||||
timeStrMults = {}
|
timeStrMults = {}
|
||||||
for velocityStr, timeStr, optimal, falloff, stackingGroup in commonData['gds']:
|
for velocityStr, timeStr, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['gds']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
velocityStr *= rangeFactor
|
velocityStr *= rangeFactor
|
||||||
timeStr *= rangeFactor
|
timeStr *= rangeFactor
|
||||||
@@ -281,23 +306,27 @@ class Distance2TpStrGetter(SmoothPointGetter):
|
|||||||
if effectName in mod.item.effects:
|
if effectName in mod.item.effects:
|
||||||
tps.append((
|
tps.append((
|
||||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||||
if 'doomsdayAOEPaint' in mod.item.effects:
|
if 'doomsdayAOEPaint' in mod.item.effects:
|
||||||
tps.append((
|
tps.append((
|
||||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||||
mod.falloff or 0, 'default'))
|
mod.falloff or 0, 'default', False, False))
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if 'remoteTargetPaintEntity' in drone.item.effects:
|
if 'remoteTargetPaintEntity' in drone.item.effects:
|
||||||
tps.extend(drone.amountActive * ((
|
tps.extend(drone.amountActive * ((
|
||||||
drone.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
drone.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
math.inf, 0, 'default', True, True),))
|
||||||
return {'tps': tps}
|
return {'tps': tps}
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
distance = x
|
distance = x
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
strMults = {}
|
strMults = {}
|
||||||
for strength, optimal, falloff, stackingGroup in commonData['tps']:
|
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tps']:
|
||||||
|
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||||
|
continue
|
||||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||||
strMult = calculateMultiplier(strMults)
|
strMult = calculateMultiplier(strMults)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from graphs.data.base import FitGraph, Input, XDef, YDef
|
from graphs.data.base import FitGraph, XDef, YDef, Input
|
||||||
from .getter import TgtSigRadius2LockTimeGetter
|
from .getter import TgtSigRadius2LockTimeGetter
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,24 +23,6 @@ import math
|
|||||||
from graphs.data.base import SmoothPointGetter
|
from graphs.data.base import SmoothPointGetter
|
||||||
|
|
||||||
|
|
||||||
class Time2SpeedGetter(SmoothPointGetter):
|
|
||||||
|
|
||||||
def _getCommonData(self, miscParams, src, tgt):
|
|
||||||
return {
|
|
||||||
'maxSpeed': src.getMaxVelocity(),
|
|
||||||
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
|
||||||
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
|
||||||
|
|
||||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
|
||||||
time = x
|
|
||||||
maxSpeed = commonData['maxSpeed']
|
|
||||||
mass = commonData['mass']
|
|
||||||
agility = commonData['agility']
|
|
||||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
|
||||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
|
||||||
return speed
|
|
||||||
|
|
||||||
|
|
||||||
class Time2DistanceGetter(SmoothPointGetter):
|
class Time2DistanceGetter(SmoothPointGetter):
|
||||||
|
|
||||||
def _getCommonData(self, miscParams, src, tgt):
|
def _getCommonData(self, miscParams, src, tgt):
|
||||||
@@ -60,3 +42,62 @@ class Time2DistanceGetter(SmoothPointGetter):
|
|||||||
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
|
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
|
||||||
distance = distance_t - distance_0
|
distance = distance_t - distance_0
|
||||||
return distance
|
return distance
|
||||||
|
|
||||||
|
|
||||||
|
class Time2SpeedGetter(SmoothPointGetter):
|
||||||
|
|
||||||
|
def _getCommonData(self, miscParams, src, tgt):
|
||||||
|
return {
|
||||||
|
'maxSpeed': src.getMaxVelocity(),
|
||||||
|
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
||||||
|
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
time = x
|
||||||
|
maxSpeed = commonData['maxSpeed']
|
||||||
|
mass = commonData['mass']
|
||||||
|
agility = commonData['agility']
|
||||||
|
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||||
|
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||||
|
return speed
|
||||||
|
|
||||||
|
|
||||||
|
class Time2MomentumGetter(Time2SpeedGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
mass = commonData['mass']
|
||||||
|
speed = Time2SpeedGetter._calculatePoint(
|
||||||
|
self, x=x, miscParams=miscParams,
|
||||||
|
src=src, tgt=tgt, commonData=commonData)
|
||||||
|
momentum = speed * mass
|
||||||
|
return momentum
|
||||||
|
|
||||||
|
|
||||||
|
class Time2BumpSpeedGetter(Time2SpeedGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||||
|
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||||
|
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||||
|
bumperMass = commonData['mass'] / 10 ** 6
|
||||||
|
bumperSpeed = Time2SpeedGetter._calculatePoint(
|
||||||
|
self, x=x, miscParams=miscParams,
|
||||||
|
src=src, tgt=tgt, commonData=commonData)
|
||||||
|
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||||
|
tgtSpeed = (2 * bumperSpeed * bumperMass) / (bumperMass + tgtMass)
|
||||||
|
return tgtSpeed
|
||||||
|
|
||||||
|
|
||||||
|
class Time2BumpDistanceGetter(Time2BumpSpeedGetter):
|
||||||
|
|
||||||
|
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||||
|
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||||
|
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||||
|
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||||
|
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||||
|
tgtInertia = miscParams['tgtInertia']
|
||||||
|
tgtSpeed = Time2BumpSpeedGetter._calculatePoint(
|
||||||
|
self, x=x, miscParams=miscParams,
|
||||||
|
src=src, tgt=tgt, commonData=commonData)
|
||||||
|
tgtDistance = tgtSpeed * tgtMass * tgtInertia
|
||||||
|
return tgtDistance
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
|
|
||||||
from graphs.data.base import FitGraph, XDef, YDef, Input
|
from graphs.data.base import FitGraph, XDef, YDef, Input
|
||||||
from .getter import Time2SpeedGetter, Time2DistanceGetter
|
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
|
||||||
|
|
||||||
|
|
||||||
class FitMobilityGraph(FitGraph):
|
class FitMobilityGraph(FitGraph):
|
||||||
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
|
|||||||
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
||||||
yDefs = [
|
yDefs = [
|
||||||
YDef(handle='speed', unit='m/s', label='Speed'),
|
YDef(handle='speed', unit='m/s', label='Speed'),
|
||||||
YDef(handle='distance', unit='km', label='Distance')]
|
YDef(handle='distance', unit='km', label='Distance'),
|
||||||
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
|
YDef(handle='momentum', unit='Gkg⋅m/s', label='Momentum'),
|
||||||
|
YDef(handle='bumpSpeed', unit='m/s', label='Target speed', selectorLabel='Bump speed'),
|
||||||
|
YDef(handle='bumpDistance', unit='km', label='Target distance traveled', selectorLabel='Bump distance')]
|
||||||
|
inputs = [
|
||||||
|
Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30)),
|
||||||
|
# Default values in target fields correspond to a random carrier/fax
|
||||||
|
Input(handle='tgtMass', unit='Mkg', label='Target mass', iconID=76, defaultValue=1300, defaultRange=(100, 2500), conditions=[(None, ('bumpSpeed', 'm/s')), (None, ('bumpDistance', 'km'))], secondaryTooltip='Defined in millions of kilograms'),
|
||||||
|
Input(handle='tgtInertia', unit=None, label='Target inertia factor', iconID=1401, defaultValue=0.015, defaultRange=(0.03, 0.1), conditions=[(None, ('bumpDistance', 'km'))], secondaryTooltip='Inertia Modifier attribute value of the target ship')]
|
||||||
srcExtraCols = ('Speed', 'Agility')
|
srcExtraCols = ('Speed', 'Agility')
|
||||||
|
|
||||||
# Calculation stuff
|
# Calculation stuff
|
||||||
|
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
|
||||||
_getters = {
|
_getters = {
|
||||||
('time', 'speed'): Time2SpeedGetter,
|
('time', 'speed'): Time2SpeedGetter,
|
||||||
('time', 'distance'): Time2DistanceGetter}
|
('time', 'distance'): Time2DistanceGetter,
|
||||||
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
|
('time', 'momentum'): Time2MomentumGetter,
|
||||||
|
('time', 'bumpSpeed'): Time2BumpSpeedGetter,
|
||||||
|
('time', 'bumpDistance'): Time2BumpDistanceGetter}
|
||||||
|
_denormalizers = {
|
||||||
|
('distance', 'km'): lambda v, src, tgt: v / 1000,
|
||||||
|
('momentum', 'Gkg⋅m/s'): lambda v, src, tgt: v / 10 ** 9,
|
||||||
|
('bumpDistance', 'km'): lambda v, src, tgt: v / 1000}
|
||||||
|
|||||||
@@ -18,23 +18,32 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
from eos.calc import calculateRangeFactor
|
||||||
from eos.utils.float import floatUnerr
|
from eos.utils.float import floatUnerr
|
||||||
from graphs.calc import calculateRangeFactor
|
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||||
|
|
||||||
|
|
||||||
def getApplicationPerKey(src, distance):
|
def getApplicationPerKey(src, distance):
|
||||||
|
inLockRange = checkLockRange(src=src, distance=distance)
|
||||||
|
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||||
applicationMap = {}
|
applicationMap = {}
|
||||||
for mod in src.item.activeModulesIter():
|
for mod in src.item.activeModulesIter():
|
||||||
if not mod.isRemoteRepping():
|
if not mod.isRemoteRepping():
|
||||||
continue
|
continue
|
||||||
applicationMap[mod] = 1 if distance is None else calculateRangeFactor(
|
if not inLockRange:
|
||||||
srcOptimalRange=mod.maxRange or 0,
|
applicationMap[mod] = 0
|
||||||
srcFalloffRange=mod.falloff or 0,
|
else:
|
||||||
distance=distance)
|
applicationMap[mod] = calculateRangeFactor(
|
||||||
|
srcOptimalRange=mod.maxRange or 0,
|
||||||
|
srcFalloffRange=mod.falloff or 0,
|
||||||
|
distance=distance)
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if not drone.isRemoteRepping():
|
if not drone.isRemoteRepping():
|
||||||
continue
|
continue
|
||||||
applicationMap[drone] = 1 if distance is None or distance <= src.item.extraAttributes['droneControlRange'] else 0
|
if not inLockRange or not inDroneRange:
|
||||||
|
applicationMap[drone] = 0
|
||||||
|
else:
|
||||||
|
applicationMap[drone] = 1
|
||||||
# Ensure consistent results - round off a little to avoid float errors
|
# Ensure consistent results - round off a little to avoid float errors
|
||||||
for k, v in applicationMap.items():
|
for k, v in applicationMap.items():
|
||||||
applicationMap[k] = floatUnerr(v)
|
applicationMap[k] = floatUnerr(v)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class YRpsMixin:
|
|||||||
isAncShield = 'shipModuleAncillaryRemoteShieldBooster' in mod.item.effects
|
isAncShield = 'shipModuleAncillaryRemoteShieldBooster' in mod.item.effects
|
||||||
isAncArmor = 'shipModuleAncillaryRemoteArmorRepairer' in mod.item.effects
|
isAncArmor = 'shipModuleAncillaryRemoteArmorRepairer' in mod.item.effects
|
||||||
rpsMap[mod] = mod.getRemoteReps(
|
rpsMap[mod] = mod.getRemoteReps(
|
||||||
spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False),
|
spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False),
|
||||||
reloadOverride=ancReload if (isAncShield or isAncArmor) else None)
|
reloadOverride=ancReload if (isAncShield or isAncArmor) else None)
|
||||||
for drone in src.item.activeDronesIter():
|
for drone in src.item.activeDronesIter():
|
||||||
if not drone.isRemoteRepping():
|
if not drone.isRemoteRepping():
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from .getter import Distance2RpsGetter, Distance2RepAmountGetter, Time2RpsGetter
|
|||||||
|
|
||||||
class FitRemoteRepsGraph(FitGraph):
|
class FitRemoteRepsGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._timeCache = TimeCache()
|
self._timeCache = TimeCache()
|
||||||
|
|
||||||
def _clearInternalCache(self, reason, extraData):
|
def _clearInternalCache(self, reason, extraData):
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ from .getter import (
|
|||||||
|
|
||||||
class FitShieldRegenGraph(FitGraph):
|
class FitShieldRegenGraph(FitGraph):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||||
|
|
||||||
# UI stuff
|
# UI stuff
|
||||||
internalName = 'shieldRegenGraph'
|
internalName = 'shieldRegenGraph'
|
||||||
name = 'Shield Regeneration'
|
name = 'Shield Regeneration'
|
||||||
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
|
|||||||
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
|
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
|
||||||
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
|
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
|
||||||
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}
|
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from .getter import AU_METERS, Distance2TimeGetter
|
|||||||
|
|
||||||
class FitWarpTimeGraph(FitGraph):
|
class FitWarpTimeGraph(FitGraph):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__(*args, **kwargs)
|
||||||
self._subspeedCache = SubwarpSpeedCache()
|
self._subspeedCache = SubwarpSpeedCache()
|
||||||
|
|
||||||
def _clearInternalCache(self, reason, extraData):
|
def _clearInternalCache(self, reason, extraData):
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ try:
|
|||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.')
|
pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.')
|
||||||
graphFrame_enabled = False
|
graphFrame_enabled = False
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
# We can get exceptions deep within matplotlib. Catch those. See GH #1046
|
# We can get exceptions deep within matplotlib. Catch those. See GH #1046
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
@@ -71,6 +73,8 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
# Remove matplotlib font cache, see #234
|
# Remove matplotlib font cache, see #234
|
||||||
try:
|
try:
|
||||||
cache_dir = mpl._get_cachedir()
|
cache_dir = mpl._get_cachedir()
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib'))
|
cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib'))
|
||||||
cache_file = os.path.join(cache_dir, 'fontList.cache')
|
cache_file = os.path.join(cache_dir, 'fontList.cache')
|
||||||
@@ -106,7 +110,9 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
legendData = []
|
legendData = []
|
||||||
chosenX = self.graphFrame.ctrlPanel.xType
|
chosenX = self.graphFrame.ctrlPanel.xType
|
||||||
chosenY = self.graphFrame.ctrlPanel.yType
|
chosenY = self.graphFrame.ctrlPanel.yType
|
||||||
self.subplot.set(xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX), ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY))
|
self.subplot.set(
|
||||||
|
xlabel=self.graphFrame.ctrlPanel.formatLabel(chosenX),
|
||||||
|
ylabel=self.graphFrame.ctrlPanel.formatLabel(chosenY))
|
||||||
|
|
||||||
mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues()
|
mainInput, miscInputs = self.graphFrame.ctrlPanel.getValues()
|
||||||
view = self.graphFrame.getView()
|
view = self.graphFrame.getView()
|
||||||
@@ -166,6 +172,8 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
legendData.append((color, lineStyle, source.shortName))
|
legendData.append((color, lineStyle, source.shortName))
|
||||||
else:
|
else:
|
||||||
legendData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName)))
|
legendData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName)))
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
pyfalog.warning('Failed to plot "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
pyfalog.warning('Failed to plot "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
||||||
self.canvas.draw()
|
self.canvas.draw()
|
||||||
@@ -186,6 +194,7 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
if minX is not None and maxX is not None:
|
if minX is not None and maxX is not None:
|
||||||
minY = min(allYs, default=None)
|
minY = min(allYs, default=None)
|
||||||
maxY = max(allYs, default=None)
|
maxY = max(allYs, default=None)
|
||||||
|
yDiff = (maxY or 0) - (minY or 0)
|
||||||
xMark = max(min(self.xMark, maxX), minX)
|
xMark = max(min(self.xMark, maxX), minX)
|
||||||
# If in top 10% of X coordinates, align labels differently
|
# If in top 10% of X coordinates, align labels differently
|
||||||
if xMark > canvasMinX + 0.9 * (canvasMaxX - canvasMinX):
|
if xMark > canvasMinX + 0.9 * (canvasMaxX - canvasMinX):
|
||||||
@@ -212,14 +221,16 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
def addYMark(val):
|
def addYMark(val):
|
||||||
if val is None:
|
if val is None:
|
||||||
return
|
return
|
||||||
|
# Round according to shown Y range - the bigger the range,
|
||||||
|
# the rougher the rounding
|
||||||
|
if yDiff != 0:
|
||||||
|
rounded = roundToPrec(val, 4, nsValue=yDiff)
|
||||||
|
else:
|
||||||
|
rounded = val
|
||||||
# If due to some bug or insufficient plot density we're
|
# If due to some bug or insufficient plot density we're
|
||||||
# out of bounds, do not add anything
|
# out of bounds, do not add anything
|
||||||
if minY <= val <= maxY:
|
if minY <= val <= maxY or minY <= rounded <= maxY:
|
||||||
if abs(val) < 0.0001:
|
yMarks.add(rounded)
|
||||||
val = 0
|
|
||||||
else:
|
|
||||||
val = roundToPrec(val, 4)
|
|
||||||
yMarks.add(val)
|
|
||||||
|
|
||||||
for source, target in iterList:
|
for source, target in iterList:
|
||||||
xs, ys = plotData[(source, target)]
|
xs, ys = plotData[(source, target)]
|
||||||
@@ -236,6 +247,8 @@ class GraphCanvasPanel(wx.Panel):
|
|||||||
src=source,
|
src=source,
|
||||||
tgt=target)
|
tgt=target)
|
||||||
addYMark(y)
|
addYMark(y)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
pyfalog.warning('Failed to get X mark for "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
pyfalog.warning('Failed to get X mark for "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
||||||
# Silently skip this mark, otherwise other marks and legend display will fail
|
# Silently skip this mark, otherwise other marks and legend display will fail
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ class GraphControlPanel(wx.Panel):
|
|||||||
fieldTextBox = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue))
|
fieldTextBox = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue))
|
||||||
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
||||||
fieldTextBox.SetToolTip(wx.ToolTip(tooltipText))
|
fieldTextBox.SetToolTip(wx.ToolTip(tooltipText))
|
||||||
fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5)
|
fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.RIGHT, 5)
|
||||||
fieldIcon = None
|
fieldIcon = None
|
||||||
if inputDef.iconID is not None:
|
if inputDef.iconID is not None:
|
||||||
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
||||||
@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
|
|||||||
|
|
||||||
self.ySubSelection.Clear()
|
self.ySubSelection.Clear()
|
||||||
for yDef in view.yDefs:
|
for yDef in view.yDefs:
|
||||||
|
if yDef.hidden and not self.graphFrame.includeHidden:
|
||||||
|
continue
|
||||||
self.ySubSelection.Append(self.formatLabel(yDef, selector=True), yDef)
|
self.ySubSelection.Append(self.formatLabel(yDef, selector=True), yDef)
|
||||||
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
||||||
self.ySubSelection.SetSelection(selectedY)
|
self.ySubSelection.SetSelection(selectedY)
|
||||||
|
|
||||||
self.xSubSelection.Clear()
|
self.xSubSelection.Clear()
|
||||||
for xDef in view.xDefs:
|
for xDef in view.xDefs:
|
||||||
|
if xDef.hidden and not self.graphFrame.includeHidden:
|
||||||
|
continue
|
||||||
self.xSubSelection.Append(self.formatLabel(xDef, selector=True), xDef)
|
self.xSubSelection.Append(self.formatLabel(xDef, selector=True), xDef)
|
||||||
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
||||||
self.xSubSelection.SetSelection(selectedX)
|
self.xSubSelection.SetSelection(selectedX)
|
||||||
|
|||||||
@@ -38,15 +38,19 @@ from .ctrlPanel import GraphControlPanel
|
|||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
REDRAW_DELAY = 500
|
||||||
|
|
||||||
|
|
||||||
class GraphFrame(AuxiliaryFrame):
|
class GraphFrame(AuxiliaryFrame):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent, includeHidden=False):
|
||||||
if not canvasPanel.graphFrame_enabled:
|
if not canvasPanel.graphFrame_enabled:
|
||||||
pyfalog.warning('Matplotlib is not enabled. Skipping initialization.')
|
pyfalog.warning('Matplotlib is not enabled. Skipping initialization.')
|
||||||
return
|
return
|
||||||
|
|
||||||
super().__init__(parent, title='Graphs', style=wx.RESIZE_BORDER, size=(520, 390))
|
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
self.includeHidden = includeHidden
|
||||||
|
|
||||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||||
|
|
||||||
@@ -71,6 +75,8 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
|
|
||||||
# Setup - graph selector
|
# Setup - graph selector
|
||||||
for view in FitGraph.views:
|
for view in FitGraph.views:
|
||||||
|
if view.hidden and not self.includeHidden:
|
||||||
|
continue
|
||||||
self.graphSelection.Append(view.name, view())
|
self.graphSelection.Append(view.name, view())
|
||||||
self.graphSelection.SetSelection(0)
|
self.graphSelection.SetSelection(0)
|
||||||
self.ctrlPanel.updateControls(layout=False)
|
self.ctrlPanel.updateControls(layout=False)
|
||||||
@@ -90,14 +96,17 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
self.mainFrame.Bind(GE.GRAPH_OPTION_CHANGED, self.OnGraphOptionChanged)
|
self.mainFrame.Bind(GE.GRAPH_OPTION_CHANGED, self.OnGraphOptionChanged)
|
||||||
self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.OnEffectiveHpToggled)
|
self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.OnEffectiveHpToggled)
|
||||||
|
|
||||||
|
self.drawTimer = wx.Timer(self)
|
||||||
|
self.Bind(wx.EVT_TIMER, self.OnDrawTimer, self.drawTimer)
|
||||||
|
|
||||||
self.Layout()
|
self.Layout()
|
||||||
self.UpdateWindowSize()
|
self.UpdateWindowSize()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def openOne(cls, parent):
|
def openOne(cls, parent, *args, **kwargs):
|
||||||
if canvasPanel.graphFrame_enabled:
|
if canvasPanel.graphFrame_enabled:
|
||||||
super().openOne(parent)
|
super().openOne(parent, *args, **kwargs)
|
||||||
|
|
||||||
def UpdateWindowSize(self):
|
def UpdateWindowSize(self):
|
||||||
curW, curH = self.GetSize()
|
curW, curH = self.GetSize()
|
||||||
@@ -110,9 +119,7 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
self.SetMinSize(newSize)
|
self.SetMinSize(newSize)
|
||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||||
mstate = wx.GetMouseState()
|
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
|
||||||
self.Close()
|
self.Close()
|
||||||
return
|
return
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -128,7 +135,10 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
for fitID in event.fitIDs:
|
for fitID in event.fitIDs:
|
||||||
self.clearCache(reason=GraphCacheCleanupReason.fitChanged, extraData=fitID)
|
self.clearCache(reason=GraphCacheCleanupReason.fitChanged, extraData=fitID)
|
||||||
self.ctrlPanel.OnFitChanged(event)
|
self.ctrlPanel.OnFitChanged(event)
|
||||||
self.draw()
|
# Data has to be recalculated - delay redraw
|
||||||
|
# to give time to finish UI update in main window
|
||||||
|
self.drawTimer.Stop()
|
||||||
|
self.drawTimer.Start(REDRAW_DELAY, True)
|
||||||
|
|
||||||
def OnFitRemoved(self, event):
|
def OnFitRemoved(self, event):
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -184,7 +194,10 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
self.ctrlPanel.refreshAxeLabels(restoreSelection=True)
|
self.ctrlPanel.refreshAxeLabels(restoreSelection=True)
|
||||||
self.Layout()
|
self.Layout()
|
||||||
self.clearCache(reason=GraphCacheCleanupReason.hpEffectivityChanged)
|
self.clearCache(reason=GraphCacheCleanupReason.hpEffectivityChanged)
|
||||||
self.draw()
|
# Data has to be recalculated - delay redraw
|
||||||
|
# to give time to finish UI update in main window
|
||||||
|
self.drawTimer.Stop()
|
||||||
|
self.drawTimer.Start(REDRAW_DELAY, True)
|
||||||
# Even if graph is not selected, keep it updated
|
# Even if graph is not selected, keep it updated
|
||||||
for idx in range(self.graphSelection.GetCount()):
|
for idx in range(self.graphSelection.GetCount()):
|
||||||
view = self.getView(idx=idx)
|
view = self.getView(idx=idx)
|
||||||
@@ -202,6 +215,10 @@ class GraphFrame(AuxiliaryFrame):
|
|||||||
self.draw()
|
self.draw()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
def OnDrawTimer(self, event):
|
||||||
|
event.Skip()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
def OnClose(self, event):
|
def OnClose(self, event):
|
||||||
self.mainFrame.Unbind(GE.FIT_RENAMED, handler=self.OnFitRenamed)
|
self.mainFrame.Unbind(GE.FIT_RENAMED, handler=self.OnFitRenamed)
|
||||||
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.OnFitChanged)
|
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.OnFitChanged)
|
||||||
|
|||||||
@@ -152,10 +152,10 @@ class BaseWrapperList(gui.display.Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
if keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
self.removeWrappers(self.getSelectedWrappers())
|
self.removeWrappers(self.getSelectedWrappers())
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
from eos.calc import calculateMultiplier
|
||||||
from eos.saveddata.damagePattern import DamagePattern
|
from eos.saveddata.damagePattern import DamagePattern
|
||||||
from eos.saveddata.fit import Fit
|
from eos.saveddata.fit import Fit
|
||||||
from eos.saveddata.targetProfile import TargetProfile
|
from eos.saveddata.targetProfile import TargetProfile
|
||||||
from service.const import TargetResistMode
|
from service.const import TargetResistMode
|
||||||
from .calc import calculateMultiplier
|
|
||||||
|
|
||||||
|
|
||||||
class BaseWrapper:
|
class BaseWrapper:
|
||||||
@@ -43,7 +43,7 @@ class BaseWrapper:
|
|||||||
if self.isFit:
|
if self.isFit:
|
||||||
return '{} ({})'.format(self.item.name, self.item.ship.item.name)
|
return '{} ({})'.format(self.item.name, self.item.ship.item.name)
|
||||||
elif self.isProfile:
|
elif self.isProfile:
|
||||||
return self.item.name
|
return self.item.fullName
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -51,13 +51,16 @@ class BaseWrapper:
|
|||||||
if self.isFit:
|
if self.isFit:
|
||||||
return '{} ({})'.format(self.item.name, self.item.ship.item.getShortName())
|
return '{} ({})'.format(self.item.name, self.item.ship.item.getShortName())
|
||||||
elif self.isProfile:
|
elif self.isProfile:
|
||||||
return self.item.name
|
return self.item.shortName
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def getMaxVelocity(self, extraMultipliers=None):
|
def getMaxVelocity(self, extraMultipliers=None, ignoreAfflictors=()):
|
||||||
if self.isFit:
|
if self.isFit:
|
||||||
if extraMultipliers:
|
if extraMultipliers or ignoreAfflictors:
|
||||||
maxVelocity = self.item.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=extraMultipliers)
|
maxVelocity = self.item.ship.getModifiedItemAttrExtended(
|
||||||
|
'maxVelocity',
|
||||||
|
extraMultipliers=extraMultipliers,
|
||||||
|
ignoreAfflictors=ignoreAfflictors)
|
||||||
else:
|
else:
|
||||||
maxVelocity = self.item.ship.getModifiedItemAttr('maxVelocity')
|
maxVelocity = self.item.ship.getModifiedItemAttr('maxVelocity')
|
||||||
elif self.isProfile:
|
elif self.isProfile:
|
||||||
@@ -68,10 +71,13 @@ class BaseWrapper:
|
|||||||
maxVelocity = None
|
maxVelocity = None
|
||||||
return maxVelocity
|
return maxVelocity
|
||||||
|
|
||||||
def getSigRadius(self, extraMultipliers=None):
|
def getSigRadius(self, extraMultipliers=None, ignoreAfflictors=()):
|
||||||
if self.isFit:
|
if self.isFit:
|
||||||
if extraMultipliers:
|
if extraMultipliers or ignoreAfflictors:
|
||||||
sigRadius = self.item.ship.getModifiedItemAttrWithExtraMods('signatureRadius', extraMultipliers=extraMultipliers)
|
sigRadius = self.item.ship.getModifiedItemAttrExtended(
|
||||||
|
'signatureRadius',
|
||||||
|
extraMultipliers=extraMultipliers,
|
||||||
|
ignoreAfflictors=ignoreAfflictors)
|
||||||
else:
|
else:
|
||||||
sigRadius = self.item.ship.getModifiedItemAttr('signatureRadius')
|
sigRadius = self.item.ship.getModifiedItemAttr('signatureRadius')
|
||||||
elif self.isProfile:
|
elif self.isProfile:
|
||||||
|
|||||||
@@ -19,7 +19,12 @@
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
versionString = "{0}".format(config.version)
|
try:
|
||||||
|
versionString = "{0}".format(config.getVersion())
|
||||||
|
except NameError:
|
||||||
|
# is caught in case we run test and there are no config values initialized
|
||||||
|
versionString = "0.0"
|
||||||
|
|
||||||
licenses = (
|
licenses = (
|
||||||
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
||||||
"All EVE-Online related materials are property of CCP hf.",
|
"All EVE-Online related materials are property of CCP hf.",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
import wx
|
import wx
|
||||||
|
|
||||||
|
import gui.globalEvents as GE
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.builtinAdditionPanes.boosterView import BoosterView
|
from gui.builtinAdditionPanes.boosterView import BoosterView
|
||||||
from gui.builtinAdditionPanes.cargoView import CargoView
|
from gui.builtinAdditionPanes.cargoView import CargoView
|
||||||
@@ -35,9 +36,10 @@ from gui.toggle_panel import TogglePanel
|
|||||||
|
|
||||||
class AdditionsPane(TogglePanel):
|
class AdditionsPane(TogglePanel):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent, mainFrame):
|
||||||
|
|
||||||
TogglePanel.__init__(self, parent, force_layout=1)
|
TogglePanel.__init__(self, parent, force_layout=1)
|
||||||
|
self.mainFrame = mainFrame
|
||||||
|
|
||||||
self.SetLabel("Additions")
|
self.SetLabel("Additions")
|
||||||
pane = self.GetContentPanel()
|
pane = self.GetContentPanel()
|
||||||
@@ -45,7 +47,7 @@ class AdditionsPane(TogglePanel):
|
|||||||
baseSizer = wx.BoxSizer(wx.HORIZONTAL)
|
baseSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
pane.SetSizer(baseSizer)
|
pane.SetSizer(baseSizer)
|
||||||
|
|
||||||
self.notebook = ChromeNotebook(pane, False)
|
self.notebook = ChromeNotebook(pane, can_add=False, tabWidthMode=1)
|
||||||
self.notebook.SetMinSize((-1, 1000))
|
self.notebook.SetMinSize((-1, 1000))
|
||||||
|
|
||||||
baseSizer.Add(self.notebook, 1, wx.EXPAND)
|
baseSizer.Add(self.notebook, 1, wx.EXPAND)
|
||||||
@@ -83,6 +85,9 @@ class AdditionsPane(TogglePanel):
|
|||||||
self.notes = NotesView(self.notebook)
|
self.notes = NotesView(self.notebook)
|
||||||
self.notebook.AddPage(self.notes, "Notes", image=notesImg, closeable=False)
|
self.notebook.AddPage(self.notes, "Notes", image=notesImg, closeable=False)
|
||||||
|
|
||||||
|
self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged)
|
||||||
|
self.mainFrame.Bind(GE.FIT_NOTES_CHANGED, self.OnNotesChanged)
|
||||||
|
|
||||||
self.notebook.SetSelection(0)
|
self.notebook.SetSelection(0)
|
||||||
|
|
||||||
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
|
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
|
||||||
@@ -106,3 +111,25 @@ class AdditionsPane(TogglePanel):
|
|||||||
self.parent.SetSashInvisible(False)
|
self.parent.SetSashInvisible(False)
|
||||||
self.parent.SetMinimumPaneSize(200)
|
self.parent.SetMinimumPaneSize(200)
|
||||||
self.parent.SetSashPosition(self.old_pos, True)
|
self.parent.SetSashPosition(self.old_pos, True)
|
||||||
|
|
||||||
|
def OnFitChanged(self, event):
|
||||||
|
event.Skip()
|
||||||
|
activeFitID = self.mainFrame.getActiveFit()
|
||||||
|
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||||
|
return
|
||||||
|
self.updateExtraText()
|
||||||
|
|
||||||
|
def OnNotesChanged(self, event):
|
||||||
|
event.Skip()
|
||||||
|
self.updateExtraText()
|
||||||
|
|
||||||
|
def updateExtraText(self):
|
||||||
|
refresh = False
|
||||||
|
for i in range(self.notebook.GetPageCount()):
|
||||||
|
page = self.notebook.GetPage(i)
|
||||||
|
if hasattr(page, 'getTabExtraText'):
|
||||||
|
refresh = True
|
||||||
|
self.notebook.SetPageTitleExtra(i, page.getTabExtraText() or '', refresh=False)
|
||||||
|
if refresh:
|
||||||
|
self.notebook.tabs_container.AdjustTabsSize()
|
||||||
|
self.notebook.Refresh()
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ class AuxiliaryFrame(wx.Frame):
|
|||||||
|
|
||||||
_instance = None
|
_instance = None
|
||||||
|
|
||||||
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None):
|
def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None, resizeable=False):
|
||||||
baseStyle = wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU
|
baseStyle = wx.FRAME_NO_TASKBAR | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU
|
||||||
|
if parent is not None:
|
||||||
|
baseStyle = baseStyle | wx.FRAME_FLOAT_ON_PARENT
|
||||||
|
if resizeable:
|
||||||
|
baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'parent': parent,
|
'parent': parent,
|
||||||
'style': baseStyle if style is None else baseStyle | style}
|
'style': baseStyle if style is None else baseStyle | style}
|
||||||
@@ -51,10 +55,10 @@ class AuxiliaryFrame(wx.Frame):
|
|||||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def openOne(cls, parent):
|
def openOne(cls, parent, *args, **kwargs):
|
||||||
"""If window is open and alive - raise it, open otherwise"""
|
"""If window is open and alive - raise it, open otherwise"""
|
||||||
if not cls._instance:
|
if not cls._instance:
|
||||||
frame = cls(parent)
|
frame = cls(parent, *args, **kwargs)
|
||||||
cls._instance = frame
|
cls._instance = frame
|
||||||
frame.Show()
|
frame.Show()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import os.path
|
import os.path
|
||||||
|
import zipfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
@@ -32,15 +33,17 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class BitmapLoader:
|
class BitmapLoader:
|
||||||
# try:
|
# Can be None if we're running from tests
|
||||||
# archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r')
|
if config.imgsZIP is None:
|
||||||
# logging.info("Using zipped image files.")
|
pyfalog.info("Using local image files.")
|
||||||
# except (IOError, TypeError):
|
archive = None
|
||||||
# logging.info("Using local image files.")
|
else:
|
||||||
# archive = None
|
try:
|
||||||
|
archive = zipfile.ZipFile(config.imgsZIP, 'r')
|
||||||
pyfalog.info("Using local image files.")
|
pyfalog.info("Using zipped image files.")
|
||||||
archive = None
|
except (IOError, TypeError):
|
||||||
|
pyfalog.info("Using local image files.")
|
||||||
|
archive = None
|
||||||
|
|
||||||
cached_bitmaps = OrderedDict()
|
cached_bitmaps = OrderedDict()
|
||||||
dont_use_cached_bitmaps = False
|
dont_use_cached_bitmaps = False
|
||||||
@@ -86,7 +89,7 @@ class BitmapLoader:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def loadBitmap(cls, name, location):
|
def loadBitmap(cls, name, location):
|
||||||
if cls.scaling_factor is None:
|
if cls.scaling_factor is None:
|
||||||
cls.scaling_factor = int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
|
cls.scaling_factor = 1 if 'wxGTK' in wx.PlatformInfo else int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
|
||||||
scale = cls.scaling_factor
|
scale = cls.scaling_factor
|
||||||
|
|
||||||
filename, img = cls.loadScaledBitmap(name, location, scale)
|
filename, img = cls.loadScaledBitmap(name, location, scale)
|
||||||
@@ -131,8 +134,8 @@ class BitmapLoader:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
img_data = cls.archive.read(path)
|
img_data = cls.archive.read(path)
|
||||||
sbuf = io.StringIO(img_data)
|
bbuf = io.BytesIO(img_data)
|
||||||
return wx.ImageFromStream(sbuf)
|
return wx.Image(bbuf)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pyfalog.warning("Missing icon file from zip: {0}".format(path))
|
pyfalog.warning("Missing icon file from zip: {0}".format(path))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -88,12 +88,12 @@ class BoosterView(d.Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
boosters = self.getSelectedBoosters()
|
boosters = self.getSelectedBoosters()
|
||||||
self.removeBoosters(boosters)
|
self.removeBoosters(boosters)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -226,3 +226,23 @@ class BoosterView(d.Display):
|
|||||||
continue
|
continue
|
||||||
boosters.append(booster)
|
boosters.append(booster)
|
||||||
return boosters
|
return boosters
|
||||||
|
|
||||||
|
def getTabExtraText(self):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
if fitID is None:
|
||||||
|
return None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(fitID)
|
||||||
|
if fit is None:
|
||||||
|
return None
|
||||||
|
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||||
|
# Amount of active boosters
|
||||||
|
if opt == 1:
|
||||||
|
amount = len([b for b in fit.boosters if b.active])
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
# Total amount of boosters
|
||||||
|
elif opt == 2:
|
||||||
|
amount = len(fit.boosters)
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import gui.display as d
|
|||||||
import gui.fitCommands as cmd
|
import gui.fitCommands as cmd
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
from gui.contextMenu import ContextMenu
|
from gui.contextMenu import ContextMenu
|
||||||
|
from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
|
||||||
from gui.utils.staticHelpers import DragDropHelper
|
from gui.utils.staticHelpers import DragDropHelper
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
from service.market import Market
|
from service.market import Market
|
||||||
@@ -58,6 +59,7 @@ class CargoView(d.Display):
|
|||||||
self.lastFitId = None
|
self.lastFitId = None
|
||||||
|
|
||||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||||
|
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||||
|
|
||||||
@@ -66,6 +68,31 @@ class CargoView(d.Display):
|
|||||||
|
|
||||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||||
|
|
||||||
|
def addItem(self, event):
|
||||||
|
item = Market.getInstance().getItem(event.itemID, eager='group')
|
||||||
|
if item is None or not item.isCharge:
|
||||||
|
event.Skip()
|
||||||
|
return
|
||||||
|
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
fit = Fit.getInstance().getFit(fitID)
|
||||||
|
|
||||||
|
if not fit:
|
||||||
|
event.Skip()
|
||||||
|
return
|
||||||
|
modifiers = wx.GetMouseState().GetModifiers()
|
||||||
|
amount = 1
|
||||||
|
if modifiers == wx.MOD_CONTROL:
|
||||||
|
amount = 10
|
||||||
|
elif modifiers == wx.MOD_ALT:
|
||||||
|
amount = 100
|
||||||
|
elif modifiers == wx.MOD_CONTROL | wx.MOD_ALT:
|
||||||
|
amount = 1000
|
||||||
|
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(
|
||||||
|
fitID=fitID, itemID=item.ID, amount=amount))
|
||||||
|
self.mainFrame.additionsPane.select('Cargo')
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def handleListDrag(self, x, y, data):
|
def handleListDrag(self, x, y, data):
|
||||||
"""
|
"""
|
||||||
Handles dragging of items from various pyfa displays which support it
|
Handles dragging of items from various pyfa displays which support it
|
||||||
@@ -104,12 +131,12 @@ class CargoView(d.Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
cargos = self.getSelectedCargos()
|
cargos = self.getSelectedCargos()
|
||||||
self.removeCargos(cargos)
|
self.removeCargos(cargos)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -214,3 +241,19 @@ class CargoView(d.Display):
|
|||||||
continue
|
continue
|
||||||
cargos.append(cargo)
|
cargos.append(cargo)
|
||||||
return cargos
|
return cargos
|
||||||
|
|
||||||
|
def getTabExtraText(self):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
if fitID is None:
|
||||||
|
return None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(fitID)
|
||||||
|
if fit is None:
|
||||||
|
return None
|
||||||
|
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||||
|
# Total amount of cargo items
|
||||||
|
if opt in (1, 2):
|
||||||
|
amount = len(fit.cargo)
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ class CommandView(d.Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
commandFits = self.getSelectedCommandFits()
|
commandFits = self.getSelectedCommandFits()
|
||||||
self.removeCommandFits(commandFits)
|
self.removeCommandFits(commandFits)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -247,3 +247,27 @@ class CommandView(d.Display):
|
|||||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitsCommand(
|
self.mainFrame.command.Submit(cmd.GuiAddCommandFitsCommand(
|
||||||
fitID=self.mainFrame.getActiveFit(),
|
fitID=self.mainFrame.getActiveFit(),
|
||||||
commandFitIDs=fitIDs))
|
commandFitIDs=fitIDs))
|
||||||
|
|
||||||
|
def getTabExtraText(self):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
if fitID is None:
|
||||||
|
return None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(fitID)
|
||||||
|
if fit is None:
|
||||||
|
return None
|
||||||
|
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||||
|
# Amount of active command fits
|
||||||
|
if opt == 1:
|
||||||
|
amount = 0
|
||||||
|
for commandFit in fit.commandFits:
|
||||||
|
info = commandFit.getCommandInfo(fitID)
|
||||||
|
if info is not None and info.active:
|
||||||
|
amount += 1
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
# Total amount of command fits
|
||||||
|
elif opt == 2:
|
||||||
|
amount = len(fit.commandFits)
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -120,12 +120,12 @@ class DroneView(Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
drones = self.getSelectedDrones()
|
drones = self.getSelectedDrones()
|
||||||
self.removeDroneStacks(drones)
|
self.removeDroneStacks(drones)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -260,7 +260,7 @@ class DroneView(Display):
|
|||||||
drone = self.drones[row]
|
drone = self.drones[row]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return
|
return
|
||||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
if event.GetModifiers() == wx.MOD_ALT:
|
||||||
self.removeDroneStacks([drone])
|
self.removeDroneStacks([drone])
|
||||||
else:
|
else:
|
||||||
self.removeDrone(drone)
|
self.removeDrone(drone)
|
||||||
@@ -337,3 +337,27 @@ class DroneView(Display):
|
|||||||
continue
|
continue
|
||||||
drones.append(drone)
|
drones.append(drone)
|
||||||
return drones
|
return drones
|
||||||
|
|
||||||
|
def getTabExtraText(self):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
if fitID is None:
|
||||||
|
return None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(fitID)
|
||||||
|
if fit is None:
|
||||||
|
return None
|
||||||
|
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||||
|
# Amount of active drones
|
||||||
|
if opt == 1:
|
||||||
|
amount = 0
|
||||||
|
for droneStack in fit.drones:
|
||||||
|
amount += droneStack.amountActive
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
# Total amount of drones
|
||||||
|
elif opt == 2:
|
||||||
|
amount = 0
|
||||||
|
for droneStack in fit.drones:
|
||||||
|
amount += droneStack.amount
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -117,6 +117,26 @@ class FighterView(wx.Panel):
|
|||||||
|
|
||||||
self.Refresh()
|
self.Refresh()
|
||||||
|
|
||||||
|
def getTabExtraText(self):
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
if fitID is None:
|
||||||
|
return None
|
||||||
|
sFit = Fit.getInstance()
|
||||||
|
fit = sFit.getFit(fitID)
|
||||||
|
if fit is None:
|
||||||
|
return None
|
||||||
|
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||||
|
# Amount of active fighter squads
|
||||||
|
if opt == 1:
|
||||||
|
amount = len([f for f in fit.fighters if f.active])
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
# Total amount of fighter squads
|
||||||
|
elif opt == 2:
|
||||||
|
amount = len(fit.fighters)
|
||||||
|
return ' ({})'.format(amount) if amount else None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class FighterDisplay(d.Display):
|
class FighterDisplay(d.Display):
|
||||||
|
|
||||||
@@ -185,12 +205,12 @@ class FighterDisplay(d.Display):
|
|||||||
|
|
||||||
def kbEvent(self, event):
|
def kbEvent(self, event):
|
||||||
keycode = event.GetKeyCode()
|
keycode = event.GetKeyCode()
|
||||||
mstate = wx.GetMouseState()
|
modifiers = event.GetModifiers()
|
||||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||||
self.unselectAll()
|
self.unselectAll()
|
||||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||||
self.selectAll()
|
self.selectAll()
|
||||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||||
fighters = self.getSelectedFighters()
|
fighters = self.getSelectedFighters()
|
||||||
self.removeFighters(fighters)
|
self.removeFighters(fighters)
|
||||||
event.Skip()
|
event.Skip()
|
||||||
@@ -295,12 +315,11 @@ class FighterDisplay(d.Display):
|
|||||||
if row != -1:
|
if row != -1:
|
||||||
col = self.getColumn(event.Position)
|
col = self.getColumn(event.Position)
|
||||||
if col != self.getColIndex(State):
|
if col != self.getColIndex(State):
|
||||||
mstate = wx.GetMouseState()
|
|
||||||
try:
|
try:
|
||||||
fighter = self.fighters[row]
|
fighter = self.fighters[row]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return
|
return
|
||||||
if mstate.GetModifiers() == wx.MOD_ALT:
|
if event.GetModifiers() == wx.MOD_ALT:
|
||||||
fighters = getSimilarFighters(self.original, fighter)
|
fighters = getSimilarFighters(self.original, fighter)
|
||||||
else:
|
else:
|
||||||
fighters = [fighter]
|
fighters = [fighter]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user