Compare commits
447 Commits
v2.15.1
...
v2.28.2dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1302ce7ee | ||
|
|
e4ea9c2ab9 | ||
|
|
9760d2c4a5 | ||
|
|
4efa9a6961 | ||
|
|
b27d4bcbbd | ||
|
|
7bff295012 | ||
|
|
01249dfd9c | ||
|
|
0cc2668c1c | ||
|
|
dfad734a0a | ||
|
|
66c901a2b0 | ||
|
|
3673e7f39e | ||
|
|
124b35d108 | ||
|
|
5181e8e95a | ||
|
|
48cc4ce9d4 | ||
|
|
07ff43b576 | ||
|
|
70969cc9fb | ||
|
|
d23bf2fa4c | ||
|
|
04c8659922 | ||
|
|
5e3b92699b | ||
|
|
1755ab4c3f | ||
|
|
26ec741094 | ||
|
|
530f7510d5 | ||
|
|
37ab704cd7 | ||
|
|
683ad3802f | ||
|
|
c882351886 | ||
|
|
9d71215c52 | ||
|
|
d2a0605abe | ||
|
|
bee6652cc6 | ||
|
|
047ef75960 | ||
|
|
32e57f8138 | ||
|
|
e891804b64 | ||
|
|
7d42b89726 | ||
|
|
6561996327 | ||
|
|
0d2ad6eac2 | ||
|
|
3b2bfd01c5 | ||
|
|
a317dab9a8 | ||
|
|
db6d8b93e9 | ||
|
|
7d585c1a62 | ||
|
|
c45e84470e | ||
|
|
ab3b40e136 | ||
|
|
b566a8bfa6 | ||
|
|
ccb395d592 | ||
|
|
3d039724c9 | ||
|
|
d0e7e7eed5 | ||
|
|
a387bc8d09 | ||
|
|
94344bd432 | ||
|
|
a976fb33f0 | ||
|
|
f850fdf0d5 | ||
|
|
0fcbedba45 | ||
|
|
5707bddacd | ||
|
|
8ed9257dfa | ||
|
|
056ae590cf | ||
|
|
7733fd38c2 | ||
|
|
fef78c971f | ||
|
|
e52ceacdb9 | ||
|
|
85b2d7af8d | ||
|
|
3e658a31bb | ||
|
|
0d2a4d4d44 | ||
|
|
bc2cdcdea7 | ||
|
|
30ad554cc0 | ||
|
|
4a85fd5d1b | ||
|
|
4d8dbe74bd | ||
|
|
016f2b44ff | ||
|
|
676794baed | ||
|
|
ca488089fd | ||
|
|
96f9b9a719 | ||
|
|
508572e08b | ||
|
|
f91e0b2e23 | ||
|
|
93ae9e0891 | ||
|
|
2ed5dbc3c7 | ||
|
|
bca8ba3114 | ||
|
|
174ac97682 | ||
|
|
2d9e873d42 | ||
|
|
33377357f6 | ||
|
|
3ee0ee7e40 | ||
|
|
2f02747b29 | ||
|
|
78b176a135 | ||
|
|
52063beea9 | ||
|
|
03a55d94e9 | ||
|
|
db256f57d1 | ||
|
|
8de6d78be0 | ||
|
|
902a00d37d | ||
|
|
523cb1467e | ||
|
|
f75de70d79 | ||
|
|
4b635f4d21 | ||
|
|
63632e09b3 | ||
|
|
df78eb5781 | ||
|
|
c1a5828d6b | ||
|
|
5377210b89 | ||
|
|
1936255f2c | ||
|
|
b3e5763cfc | ||
|
|
7442f315c9 | ||
|
|
433c9555bf | ||
|
|
d1345fc71e | ||
|
|
31cae0e54b | ||
|
|
b2cc3ae600 | ||
|
|
62d1d6a06d | ||
|
|
83fa567321 | ||
|
|
9b315b5870 | ||
|
|
3094fd32fc | ||
|
|
c3f1824a84 | ||
|
|
b558ae3810 | ||
|
|
c38f05902a | ||
|
|
181e1e1e30 | ||
|
|
3fc26254ae | ||
|
|
ca8822c455 | ||
|
|
4c55d32086 | ||
|
|
c13817708e | ||
|
|
39cab4be72 | ||
|
|
f80473d073 | ||
|
|
0d9be1b174 | ||
|
|
83b0b3ff0e | ||
|
|
c4b490907d | ||
|
|
f89fe4d5d7 | ||
|
|
81dced03bf | ||
|
|
27d3bea8d8 | ||
|
|
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 | ||
|
|
aec5a46452 | ||
|
|
588f9a4490 | ||
|
|
4ba948c6f9 | ||
|
|
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 | ||
|
|
eacc97160b | ||
|
|
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 | ||
|
|
78423f67dd | ||
|
|
2962ce1945 | ||
|
|
0063d2955e | ||
|
|
9787a18666 | ||
|
|
d8cd3197b5 | ||
|
|
e07c4f65ab | ||
|
|
c3108f773a | ||
|
|
a400821268 | ||
|
|
ca4bac07da | ||
|
|
0038eacc3f | ||
|
|
4bd633a0d4 | ||
|
|
ee837f0b04 | ||
|
|
016e18cc89 | ||
|
|
1d6ac53183 | ||
|
|
1e32dfa463 | ||
|
|
4431753570 | ||
|
|
6fdb57318c | ||
|
|
649db019de | ||
|
|
1d528be0ef | ||
|
|
2d6f6df83d | ||
|
|
7fa998f276 | ||
|
|
6a3157a4c8 | ||
|
|
74efa50576 | ||
|
|
61e32d0b1b | ||
|
|
bf04d26a7b | ||
|
|
e99be78c54 | ||
|
|
e386232de1 | ||
|
|
a15896044d | ||
|
|
72c74513ce | ||
|
|
34d6d13cb2 | ||
|
|
323bb8e064 | ||
|
|
b628f8ef9b | ||
|
|
1900216d36 | ||
|
|
c045ed81c1 | ||
|
|
39edec60e3 | ||
|
|
259214e907 | ||
|
|
2e1c53392d | ||
|
|
7a77d12686 | ||
|
|
2217aff5ab | ||
|
|
400d0aaa22 | ||
|
|
bfc928934c | ||
|
|
395c17270e | ||
|
|
1b40467775 | ||
|
|
27e3e53868 | ||
|
|
00b9884c68 | ||
|
|
b05bd04801 | ||
|
|
7ba5585b83 | ||
|
|
9386ba3fb9 | ||
|
|
1321e70035 | ||
|
|
e2d943b0b0 | ||
|
|
424b769ba9 | ||
|
|
3671f10c9c | ||
|
|
935ecd0ea7 | ||
|
|
92e15ece72 | ||
|
|
a5c1875a29 | ||
|
|
9146c0f2c6 | ||
|
|
a917207e07 | ||
|
|
c21b92945f | ||
|
|
049bd10797 | ||
|
|
b486276167 | ||
|
|
e4df215e47 | ||
|
|
33cb332978 | ||
|
|
7c8f4d51bb | ||
|
|
66bf7b3dc6 | ||
|
|
8bb1d43d0c | ||
|
|
3d70ca941c | ||
|
|
04a1e95730 | ||
|
|
9fddb64ef9 | ||
|
|
e883035120 | ||
|
|
4d2da8e52e | ||
|
|
c1df335f08 |
123
.appveyor.yml
123
.appveyor.yml
@@ -1,126 +1,78 @@
|
||||
image: Visual Studio 2019
|
||||
clone_depth: 400
|
||||
|
||||
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:
|
||||
|
||||
- PYTHON: "C:\\Python36"
|
||||
PYTHON_VERSION: "3.6.x"
|
||||
PYTHON_ARCH: "32"
|
||||
init:
|
||||
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
- PYTHON: "C:\\Python37-x64"
|
||||
# Should be enabled only for build process debugging
|
||||
# init:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
install:
|
||||
# If there is a newer build queued for the same PR, cancel this one.
|
||||
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
||||
# 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." }
|
||||
- ps: echo("OS version:")
|
||||
- ps: "[System.Environment]::OSVersion.Version"
|
||||
|
||||
- ECHO "Filesystem root:"
|
||||
- ps: "ls \"C:/\""
|
||||
- ps: echo("Filesystem - root:")
|
||||
- ps: "ls \"C:\\\""
|
||||
|
||||
- ECHO "Filesystem projects root:"
|
||||
- ps: echo("Filesystem - projects root:")
|
||||
- ps: "ls \"C:\\projects\\\""
|
||||
|
||||
- ECHO "Filesystem pyfa root:"
|
||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\""
|
||||
- ps: echo("Filesystem - pyfa root:")
|
||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\\""
|
||||
|
||||
- ECHO "Installed SDKs:"
|
||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
||||
- ps: echo("Filesystem - installed SDKs:")
|
||||
- ps: "ls \"C:\\Program Files (x86)\\Windows Kits\\\""
|
||||
|
||||
# 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
|
||||
# the parent CMD process).
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- cmd: "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
|
||||
- "python --version"
|
||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
- cmd: "python --version"
|
||||
- cmd: "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
|
||||
# Upgrade to the latest version of pip to avoid it displaying warnings
|
||||
# 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
|
||||
# compiled extensions and are not provided as pre-built wheel packages,
|
||||
# pip will build them from source using the MSVC compiler matching the
|
||||
# target Python version and architecture
|
||||
- ECHO "Install pip requirements:"
|
||||
- "python -m pip install -r requirements.txt"
|
||||
- "python -m pip install PyInstaller"
|
||||
- ps: echo("Install pip requirements:")
|
||||
# This one is needed to build wxpython 4.0.6 on windows
|
||||
- cmd: "python -m pip install pathlib2"
|
||||
- cmd: "python -m pip install -r requirements.txt"
|
||||
- cmd: "python -m pip install PyInstaller==3.6"
|
||||
|
||||
before_build:
|
||||
# directory that will contain the built files
|
||||
- ps: $env:PYFA_DIST_DIR = "c:\projects\$env:APPVEYOR_PROJECT_SLUG\dist"
|
||||
- ps: $env:PYFA_VERSION = (python ./scripts/dump_version.py)
|
||||
- ps: echo("pyfa version ")
|
||||
- ps: echo ($env:PYFA_VERSION)
|
||||
- ps: echo("pyfa version $env:PYFA_VERSION")
|
||||
|
||||
build_script:
|
||||
- ECHO "Build pyfa:"
|
||||
|
||||
- ps: echo("Build pyfa:")
|
||||
# Build gamedata DB
|
||||
- "python db_update.py"
|
||||
##########
|
||||
# PyInstaller - create binaries for pyfa
|
||||
##########
|
||||
- cmd: "python db_update.py"
|
||||
# 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)
|
||||
- ps: xcopy /y dist_assets\win\pyfa.exe.manifest $env:PYFA_DIST_DIR\pyfa\
|
||||
# Not really sure if this is needed, but why not
|
||||
- ps: xcopy /y dist_assets\win\Microsoft.VC90.CRT.manifest $env:PYFA_DIST_DIR\pyfa\
|
||||
|
||||
##########
|
||||
# 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"
|
||||
# InnoScript EXE building. This is in a separate script because I don't feel like copying over the logic to AppVeyor script right now...
|
||||
- cmd: "python dist_assets/win/dist.py"
|
||||
- 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:
|
||||
- ps: "ls \"./\""
|
||||
#- ps: "ls \"C:\\projects\\pyfa\\build\\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
|
||||
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*"
|
||||
|
||||
test_script:
|
||||
#- tox
|
||||
#- "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"
|
||||
# Ha... we're just building
|
||||
|
||||
artifacts:
|
||||
# Archive the generated packages in the ci.appveyor.com build report.
|
||||
- path: pyfa*-win.zip
|
||||
- path: pyfa*-win.exe
|
||||
#- path: pyfa_debug.zip
|
||||
# name: Pyfa_debug
|
||||
|
||||
deploy:
|
||||
tag: $(pyfa_version)
|
||||
@@ -128,10 +80,9 @@ deploy:
|
||||
description: 'Release description'
|
||||
provider: GitHub
|
||||
auth_token:
|
||||
secure: BfNHO66ff5hVx2O2ORbl49X0U/5h2V2T0IuRZDwm7fd1HvsVluF0wRCbl29oRp1M
|
||||
secure: X+U3hOAMTt7HGXCR/LXaGNF6qyhUXetrjz5+xlWiNJQ3XEdzhZZmHK75m0Hm6qre
|
||||
draft: true
|
||||
force_update: false
|
||||
# deploy on tag push only
|
||||
on:
|
||||
APPVEYOR_REPO_TAG: true # deploy on tag push only
|
||||
#on_success:
|
||||
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
||||
#
|
||||
APPVEYOR_REPO_TAG: true
|
||||
|
||||
@@ -3,13 +3,12 @@ language: python
|
||||
git:
|
||||
depth: 400
|
||||
python:
|
||||
- 3.6
|
||||
- 3.8
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode11.3
|
||||
language: generic
|
||||
env: PYTHON=3.6.1
|
||||
before_install:
|
||||
- bash scripts/setup-osx.sh
|
||||
install:
|
||||
@@ -22,7 +21,7 @@ before_deploy:
|
||||
deploy:
|
||||
provider: releases
|
||||
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: "dist/pyfa-*.zip"
|
||||
skip_cleanup: true
|
||||
|
||||
@@ -49,6 +49,8 @@ def DBInMemory_test():
|
||||
gamedata_version = gamedata_session.execute(
|
||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
|
||||
).fetchone()[0]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
print("Missing gamedata version.")
|
||||
gamedata_version = None
|
||||
@@ -72,7 +74,7 @@ def DBInMemory_test():
|
||||
# noinspection PyPep8
|
||||
#from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
|
||||
# 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 saveddata_connectionstring == "sqlite:///:memory:":
|
||||
|
||||
@@ -233,6 +233,8 @@ def defLogging():
|
||||
# reset=False,
|
||||
)
|
||||
])
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print("Critical error attempting to setup logging. Falling back to console only.")
|
||||
logging_setup = NestedSetup([
|
||||
|
||||
452
db_update.py
452
db_update.py
@@ -23,6 +23,7 @@ import functools
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
@@ -32,7 +33,7 @@ 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 = 1
|
||||
GAMEDATA_SCHEMA_VERSION = 4
|
||||
|
||||
|
||||
def db_needs_update():
|
||||
@@ -40,7 +41,7 @@ def db_needs_update():
|
||||
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:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
# If we have no source data - return None; should not update in this case
|
||||
except:
|
||||
@@ -61,7 +62,7 @@ def db_needs_update():
|
||||
db_schema_version = int(row[0])
|
||||
cursor.close()
|
||||
db.close()
|
||||
except KeyboardInterrupt:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print('Error when fetching gamedata DB metadata')
|
||||
@@ -88,52 +89,177 @@ def update_db():
|
||||
# Create the database tables
|
||||
eos.db.gamedata_meta.create_all()
|
||||
|
||||
# Config dict
|
||||
tables = {
|
||||
'clonegrades': ('fsd_lite', eos.gamedata.AlphaCloneSkill),
|
||||
'dogmaattributes': ('bulkdata', eos.gamedata.AttributeInfo),
|
||||
'dogmaeffects': ('bulkdata', eos.gamedata.Effect),
|
||||
'dogmatypeattributes': ('bulkdata', eos.gamedata.Attribute),
|
||||
'dogmatypeeffects': ('bulkdata', eos.gamedata.ItemEffect),
|
||||
'dogmaunits': ('bulkdata', eos.gamedata.Unit),
|
||||
'evecategories': ('fsd_lite', eos.gamedata.Category),
|
||||
'evegroups': ('fsd_lite', eos.gamedata.Group),
|
||||
'metagroups': ('fsd_binary', eos.gamedata.MetaGroup),
|
||||
'evetypes': ('fsd_lite', eos.gamedata.Item),
|
||||
'traits': ('phobos', eos.gamedata.Traits),
|
||||
'metadata': ('phobos', eos.gamedata.MetaData),
|
||||
'marketgroups': ('fsd_binary', eos.gamedata.MarketGroup)}
|
||||
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
|
||||
|
||||
fieldMapping = {
|
||||
'marketgroups': {
|
||||
'id': 'marketGroupID',
|
||||
'name': 'marketGroupName'},
|
||||
'metagroups': {
|
||||
'id': 'metaGroupID'}}
|
||||
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)
|
||||
|
||||
rowsInValues = (
|
||||
'evetypes',
|
||||
'evegroups',
|
||||
'evecategories',
|
||||
'marketgroups',
|
||||
'metagroups')
|
||||
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'] == 'Capsule' or
|
||||
row['groupID'] == 4033 # destructible effect beacons
|
||||
):
|
||||
row['published'] = True
|
||||
# Nearly useless and clutter search results too much
|
||||
elif row['typeName'].startswith('Limited Synth '):
|
||||
row['published'] = False
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
for k, v in list(data.items()):
|
||||
v['iconID'] = k
|
||||
new.append(v)
|
||||
return new
|
||||
|
||||
def convertClones(data):
|
||||
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,
|
||||
1983) # the "container" for the abyssal environments
|
||||
):
|
||||
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({
|
||||
@@ -144,18 +270,25 @@ def update_db():
|
||||
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')
|
||||
|
||||
return newData
|
||||
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 convertTraits(data):
|
||||
def processTraits():
|
||||
print('processing traits')
|
||||
data = _readData('phobos', 'traits')
|
||||
|
||||
def convertSection(sectionData):
|
||||
sectionLines = []
|
||||
@@ -182,9 +315,42 @@ def update_db():
|
||||
traitLine = '<br />\n<br />\n'.join(typeLines)
|
||||
newRow = {'typeID': typeId, 'traitText': traitLine}
|
||||
newData.append(newRow)
|
||||
return newData
|
||||
|
||||
def fillReplacements(tables):
|
||||
_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
|
||||
@@ -196,7 +362,6 @@ def update_db():
|
||||
return True
|
||||
return False
|
||||
|
||||
print('finding replacements')
|
||||
skillReqAttribs = {
|
||||
182: 277,
|
||||
183: 278,
|
||||
@@ -208,18 +373,18 @@ def update_db():
|
||||
# Get data on type groups
|
||||
# Format: {type ID: group ID}
|
||||
typesGroups = {}
|
||||
for row in tables['evetypes']:
|
||||
for row in eveTypesData:
|
||||
typesGroups[row['typeID']] = row['groupID']
|
||||
# Get data on item effects
|
||||
# Format: {type ID: set(effect, IDs)}
|
||||
typesEffects = {}
|
||||
for row in tables['dogmatypeeffects']:
|
||||
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 tables['dogmatypeattributes']:
|
||||
for row in dogmaTypeAttributesData:
|
||||
attributeID = row['attributeID']
|
||||
if attributeID in skillReqAttribsFlat:
|
||||
typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
|
||||
@@ -258,13 +423,13 @@ def update_db():
|
||||
typeSkillReqs[skillType] = skillLevel
|
||||
# Format: {group ID: category ID}
|
||||
groupCategories = {}
|
||||
for row in tables['evegroups']:
|
||||
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 tables['evetypes']:
|
||||
for row in eveTypesData:
|
||||
typeID = row['typeID']
|
||||
# Ignore items outside of categories we need
|
||||
if groupCategories[typesGroups[typeID]] not in (
|
||||
@@ -301,134 +466,63 @@ def update_db():
|
||||
if compareAttrs(type1[1], type2[1]):
|
||||
replacements.setdefault(type1[0], set()).add(type2[0])
|
||||
replacements.setdefault(type2[0], set()).add(type1[0])
|
||||
# Put this data into types table so that normal process hooks it up
|
||||
for row in tables['evetypes']:
|
||||
row['replacements'] = ','.join('{}'.format(tid) for tid in sorted(replacements.get(row['typeID'], ())))
|
||||
# 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))
|
||||
|
||||
data = {}
|
||||
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)
|
||||
|
||||
# Dump all data to memory so we can easely cross check ignored rows
|
||||
for jsonName, (minerName, cls) in tables.items():
|
||||
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
|
||||
tableData = json.load(f)
|
||||
if jsonName in rowsInValues:
|
||||
newTableData = []
|
||||
for k, v in tableData.items():
|
||||
row = {}
|
||||
row.update(v)
|
||||
if 'id' not in row:
|
||||
row['id'] = int(k)
|
||||
newTableData.append(row)
|
||||
tableData = newTableData
|
||||
if jsonName == 'icons':
|
||||
tableData = convertIcons(tableData)
|
||||
if jsonName == 'traits':
|
||||
tableData = convertTraits(tableData)
|
||||
if jsonName == 'clonegrades':
|
||||
tableData = convertClones(tableData)
|
||||
data[jsonName] = tableData
|
||||
eveTypesData = processEveTypes()
|
||||
eveGroupsData = processEveGroups()
|
||||
processEveCategories()
|
||||
processDogmaAttributes()
|
||||
dogmaTypeAttributesData = processDogmaTypeAttributes(eveTypesData)
|
||||
processDynamicItemAttributes()
|
||||
processDogmaEffects()
|
||||
dogmaTypeEffectsData = processDogmaTypeEffects(eveTypesData)
|
||||
processDogmaUnits()
|
||||
processMarketGroups()
|
||||
processMetaGroups()
|
||||
processCloneGrades()
|
||||
processTraits()
|
||||
processMetadata()
|
||||
|
||||
fillReplacements(data)
|
||||
|
||||
# Set with typeIDs which we will have in our database
|
||||
# Sometimes CCP unpublishes some items we want to have published, we
|
||||
# can do it here - just add them to initial set
|
||||
eveTypes = set()
|
||||
for row in data['evetypes']:
|
||||
if (
|
||||
row['published'] or
|
||||
row['typeName'] == 'Capsule' or
|
||||
# group Ship Modifiers, for items like tactical t3 ship modes
|
||||
row['groupID'] == 1306 or
|
||||
# Civilian weapons
|
||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) 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) or
|
||||
# Dark Blood Tracking Disruptor (drops, but rarely)
|
||||
row['typeID'] == 32416
|
||||
):
|
||||
eveTypes.add(row['typeID'])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file in ('evetypes', 'dogmatypeeffects', 'dogmatypeattributes') and row['typeID'] not in eveTypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Loop through each json file and write it away, checking ignored rows
|
||||
for jsonName, table in data.items():
|
||||
fieldMap = fieldMapping.get(jsonName, {})
|
||||
tmp = []
|
||||
|
||||
print('processing {}'.format(jsonName))
|
||||
|
||||
for row in table:
|
||||
# We don't care about some kind of rows, filter it out if so
|
||||
if not isIgnored(jsonName, row):
|
||||
if (
|
||||
jsonName == 'evetypes' and (
|
||||
# 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
|
||||
|
||||
instance = tables[jsonName][1]()
|
||||
# fix for issue 80
|
||||
if jsonName is 'icons' and 'res:/ui/texture/icons/' in str(row['iconFile']).lower():
|
||||
row['iconFile'] = row['iconFile'].lower().replace('res:/ui/texture/icons/', '').replace('.png', '')
|
||||
# with res:/ui... references, it points to the actual icon file (including it's size variation of #_size_#)
|
||||
# strip this info out and get the identifying info
|
||||
split = row['iconFile'].split('_')
|
||||
if len(split) == 3:
|
||||
row['iconFile'] = '{}_{}'.format(split[0], split[2])
|
||||
if jsonName is 'icons' and 'modules/' in str(row['iconFile']).lower():
|
||||
row['iconFile'] = row['iconFile'].lower().replace('modules/', '').replace('.png', '')
|
||||
|
||||
if jsonName is 'clonegrades':
|
||||
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'])
|
||||
|
||||
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)
|
||||
|
||||
# quick and dirty hack to get this data in
|
||||
with open(os.path.join(JSON_DIR, 'fsd_binary', 'dynamicitemattributes.json'), encoding='utf-8') as f:
|
||||
bulkdata = json.load(f)
|
||||
for mutaID, data in bulkdata.items():
|
||||
muta = eos.gamedata.DynamicItem()
|
||||
muta.typeID = mutaID
|
||||
muta.resultingTypeID = data['inputOutputMapping'][0]['resultingType']
|
||||
eos.db.gamedata_session.add(muta)
|
||||
|
||||
for x in data['inputOutputMapping'][0]['applicableTypes']:
|
||||
item = eos.gamedata.DynamicItemItem()
|
||||
item.typeID = mutaID
|
||||
item.applicableTypeID = x
|
||||
eos.db.gamedata_session.add(item)
|
||||
|
||||
for attrID, attrData in data['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)
|
||||
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()
|
||||
@@ -436,7 +530,7 @@ def update_db():
|
||||
metadata_schema_version.field_value = GAMEDATA_SCHEMA_VERSION
|
||||
eos.db.gamedata_session.add(metadata_schema_version)
|
||||
|
||||
eos.db.gamedata_session.commit()
|
||||
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
|
||||
|
||||
@@ -30,7 +30,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'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")
|
||||
|
||||
@@ -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_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")
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
||||
#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
|
||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
||||
#endif
|
||||
@@ -30,7 +26,6 @@
|
||||
#endif
|
||||
|
||||
[Setup]
|
||||
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
@@ -41,6 +36,9 @@ AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
CloseApplications=yes
|
||||
DefaultDirName={pf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
@@ -49,7 +47,6 @@ OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename={#MyOutputFile}
|
||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||
SolidCompression=yes
|
||||
CloseApplications=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
@@ -83,6 +80,7 @@ Type: files; Name: "{app}\*.pyc"
|
||||
|
||||
[Code]
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function IsAppRunning(const FileName : string): Boolean;
|
||||
var
|
||||
FSWbemLocator: Variant;
|
||||
@@ -99,6 +97,7 @@ begin
|
||||
FSWbemLocator := Unassigned;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
procedure RemoveFromVirtualStore;
|
||||
var
|
||||
VirtualStore,FileName,FilePath:String;
|
||||
@@ -115,6 +114,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
if(IsAppRunning( 'pyfa.exe' )) then
|
||||
@@ -127,54 +127,61 @@ begin
|
||||
end
|
||||
end;
|
||||
|
||||
function GetUninstallString: string;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function GetUninstallString(): String;
|
||||
var
|
||||
sUnInstPath: string;
|
||||
sUnInstPath: String;
|
||||
sUnInstallString: String;
|
||||
begin
|
||||
Result := '';
|
||||
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1'); //Your App GUID/ID
|
||||
sUnInstallString := '';
|
||||
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;
|
||||
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
|
||||
Result := (GetUninstallString() <> '');
|
||||
end;
|
||||
|
||||
function InitializeSetup: Boolean;
|
||||
var
|
||||
V: Integer;
|
||||
iResultCode: Integer;
|
||||
sUnInstallString: string;
|
||||
iOldVersionMajor: Cardinal;
|
||||
iOldVersionMinor: Cardinal;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
Result := True; // in case when no previous version is found
|
||||
if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', 'UninstallString') then //Your App GUID/ID
|
||||
if (CurStep=ssInstall) then
|
||||
begin
|
||||
RegQueryDWordValue(HKEY_LOCAL_MACHINE,
|
||||
'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.
|
||||
if (IsUpgrade()) then
|
||||
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
|
||||
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
|
||||
UnInstallOldVersion();
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</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">
|
||||
<application>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
|
||||
@@ -20,7 +20,6 @@ added_files = [
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../dist_assets/win/pyfa.ico', '.'),
|
||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||
(requests.certs.where(), '.'), # is this needed anymore?
|
||||
('../../eve.db', '.'),
|
||||
('../../README.md', '.'),
|
||||
@@ -30,7 +29,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'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
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
@@ -17,24 +17,37 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import MetaData, create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
|
||||
from . import migration
|
||||
from eos import config
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
pyfalog.info("Initializing database")
|
||||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
||||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
||||
|
||||
|
||||
class ReadOnlyException(Exception):
|
||||
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')
|
||||
gamedata_connectionstring = config.gamedata_connectionstring
|
||||
if callable(gamedata_connectionstring):
|
||||
@@ -42,9 +55,26 @@ if callable(gamedata_connectionstring):
|
||||
else:
|
||||
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.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')
|
||||
# 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(
|
||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'dump_time'"
|
||||
).fetchone()[0]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.warning("Missing gamedata version.")
|
||||
pyfalog.critical(e)
|
||||
@@ -82,10 +114,10 @@ sd_lock = threading.RLock()
|
||||
pyfalog.debug('Importing gamedata DB scheme')
|
||||
# Import all the definitions for all our database stuff
|
||||
# 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')
|
||||
# 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
|
||||
|
||||
pyfalog.debug('Importing gamedata queries')
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__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")})
|
||||
@@ -33,9 +33,6 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("description", String),
|
||||
Column("raceID", Integer),
|
||||
Column("factionID", Integer),
|
||||
Column("volume", Float),
|
||||
Column("mass", Float),
|
||||
Column("capacity", Float),
|
||||
Column("published", Boolean),
|
||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||
Column("iconID", Integer),
|
||||
@@ -44,7 +41,9 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("metaLevel", Integer),
|
||||
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID"), index=True),
|
||||
Column("variationParentTypeID", Integer, ForeignKey("invtypes.typeID"), index=True),
|
||||
Column("replacements", String))
|
||||
Column("replacements", String),
|
||||
Column("reqskills", String),
|
||||
Column("requiredfor", String))
|
||||
|
||||
from .traits import traits_table # noqa
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ from sqlalchemy.orm import aliased, exc, join
|
||||
from sqlalchemy.sql import and_, or_, select
|
||||
|
||||
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.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 = {}
|
||||
configVal = getattr(eos.config, "gamedataCache", None)
|
||||
@@ -64,7 +64,7 @@ else:
|
||||
return deco
|
||||
|
||||
|
||||
def sqlizeString(line):
|
||||
def sqlizeNormalString(line):
|
||||
# Escape backslashes first, as they will be as escape symbol in queries
|
||||
# Then escape percent and underscore signs
|
||||
# Finally, replace generic wildcards with sql-style wildcards
|
||||
@@ -79,29 +79,39 @@ itemNameMap = {}
|
||||
def getItem(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(lookfor)
|
||||
item = get_gamedata_session().query(Item).get(lookfor)
|
||||
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):
|
||||
if lookfor in itemNameMap:
|
||||
id = itemNameMap[lookfor]
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(id)
|
||||
item = get_gamedata_session().query(Item).get(id)
|
||||
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:
|
||||
# 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()
|
||||
if item is not None:
|
||||
itemNameMap[lookfor] = item.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
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):
|
||||
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:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -109,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
|
||||
|
||||
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||
# 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)
|
||||
|
||||
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
||||
@@ -125,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.
|
||||
# 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
|
||||
gamedata_session.expunge(item)
|
||||
get_gamedata_session().expunge(item)
|
||||
return item
|
||||
|
||||
|
||||
@@ -148,7 +158,7 @@ def getItems(lookfor, eager=None):
|
||||
|
||||
if len(toGet) > 0:
|
||||
# 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:
|
||||
cache[(item.ID, None)] = item
|
||||
results += items
|
||||
@@ -162,9 +172,9 @@ def getItems(lookfor, eager=None):
|
||||
def getAlphaClone(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(AlphaClone).get(lookfor)
|
||||
item = get_gamedata_session().query(AlphaClone).get(lookfor)
|
||||
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:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -172,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
|
||||
|
||||
def getAlphaCloneList(eager=None):
|
||||
eager = processEager(eager)
|
||||
clones = gamedata_session.query(AlphaClone).options(*eager).all()
|
||||
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
|
||||
return clones
|
||||
|
||||
|
||||
@@ -183,19 +193,19 @@ groupNameMap = {}
|
||||
def getGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(lookfor)
|
||||
group = get_gamedata_session().query(Group).get(lookfor)
|
||||
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):
|
||||
if lookfor in groupNameMap:
|
||||
id = groupNameMap[lookfor]
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(id)
|
||||
group = get_gamedata_session().query(Group).get(id)
|
||||
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:
|
||||
# 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()
|
||||
if group is not None:
|
||||
groupNameMap[lookfor] = group.ID
|
||||
else:
|
||||
@@ -210,21 +220,21 @@ categoryNameMap = {}
|
||||
def getCategory(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(lookfor)
|
||||
category = get_gamedata_session().query(Category).get(lookfor)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in categoryNameMap:
|
||||
id = categoryNameMap[lookfor]
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(id)
|
||||
category = get_gamedata_session().query(Category).get(id)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == id).first()
|
||||
else:
|
||||
# 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()
|
||||
if category is not None:
|
||||
categoryNameMap[lookfor] = category.ID
|
||||
@@ -240,21 +250,21 @@ metaGroupNameMap = {}
|
||||
def getMetaGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in metaGroupNameMap:
|
||||
id = metaGroupNameMap[lookfor]
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(id)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == id).first()
|
||||
else:
|
||||
# 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()
|
||||
if metaGroup is not None:
|
||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||
@@ -264,16 +274,16 @@ def getMetaGroup(lookfor, eager=None):
|
||||
|
||||
|
||||
def getMetaGroups():
|
||||
return gamedata_session.query(MetaGroup).all()
|
||||
return get_gamedata_session().query(MetaGroup).all()
|
||||
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getMarketGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
|
||||
else:
|
||||
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
|
||||
MarketGroup.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
@@ -285,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
|
||||
addedIds = set(rootNodeIds)
|
||||
while 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
|
||||
|
||||
|
||||
@@ -299,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
|
||||
raise TypeError("Need integer or string as argument")
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -314,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
if not hasattr(join, "__iter__"):
|
||||
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(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
||||
else:
|
||||
@@ -325,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
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")
|
||||
def searchSkills(nameLike, where=None, eager=None):
|
||||
if not isinstance(nameLike, str):
|
||||
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(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
||||
else:
|
||||
@@ -352,7 +383,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
|
||||
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||
filter = processWhere(itemfilter, where)
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
vars = get_gamedata_session().query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
|
||||
if vars:
|
||||
return vars
|
||||
@@ -360,7 +391,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
||||
filter = processWhere(itemfilter, where)
|
||||
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()
|
||||
|
||||
return vars
|
||||
@@ -375,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
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:
|
||||
result = None
|
||||
return result
|
||||
@@ -384,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
@cachedQuery(1, "field")
|
||||
def getMetaData(field):
|
||||
if isinstance(field, str):
|
||||
data = gamedata_session.query(MetaData).get(field)
|
||||
data = get_gamedata_session().query(MetaData).get(field)
|
||||
else:
|
||||
raise TypeError("Need string as argument")
|
||||
return data
|
||||
@@ -403,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
|
||||
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
||||
from_obj=[join(Attribute, Item)])
|
||||
|
||||
result = gamedata_session.execute(q).fetchall()
|
||||
result = get_gamedata_session().execute(q).fetchall()
|
||||
return result
|
||||
|
||||
|
||||
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")
|
||||
@@ -416,9 +447,9 @@ def getDynamicItem(itemID, eager=None):
|
||||
try:
|
||||
if isinstance(itemID, int):
|
||||
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:
|
||||
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:
|
||||
raise TypeError("Need integer as argument")
|
||||
except exc.NoResultFound:
|
||||
@@ -426,23 +457,7 @@ def getDynamicItem(itemID, eager=None):
|
||||
return result
|
||||
|
||||
|
||||
def getRequiredFor(itemID, attrMapping):
|
||||
Attribute1 = aliased(Attribute)
|
||||
Attribute2 = aliased(Attribute)
|
||||
|
||||
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
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getAllImplantSets():
|
||||
implantSets = get_gamedata_session().query(ImplantSet).all()
|
||||
return implantSets
|
||||
|
||||
@@ -8,43 +8,25 @@ many upgrade files as there are database versions (version 5 would include
|
||||
upgrade files 1-5)
|
||||
"""
|
||||
|
||||
import pkgutil
|
||||
import re
|
||||
|
||||
from eos.utils.pyinst_support import iterNamespace
|
||||
|
||||
updates = {}
|
||||
appVersion = 0
|
||||
|
||||
prefix = __name__ + "."
|
||||
|
||||
# load modules to work based with and without pyinstaller
|
||||
# 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:
|
||||
for modName in iterNamespace(__name__, __path__):
|
||||
# loop through python files, extracting update number and function, and
|
||||
# adding it to a list
|
||||
modname_tail = modname.rsplit('.', 1)[-1]
|
||||
module = __import__(modname, fromlist=True)
|
||||
modname_tail = modName.rsplit('.', 1)[-1]
|
||||
m = re.match("^upgrade(?P<index>\d+)$", modname_tail)
|
||||
if not m:
|
||||
continue
|
||||
index = int(m.group("index"))
|
||||
appVersion = max(appVersion, index)
|
||||
module = __import__(modName, fromlist=True)
|
||||
upgrade = getattr(module, "upgrade", False)
|
||||
if upgrade:
|
||||
updates[index] = upgrade
|
||||
|
||||
@@ -33,9 +33,13 @@ def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_session.execute(commandFits_table.insert(),
|
||||
{"boosterID": value, "boostedID": boosted, "active": 1})
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
saveddata_session.commit()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# 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
|
||||
|
||||
@@ -4235,6 +4235,8 @@ def upgrade(saveddata_engine):
|
||||
|
||||
# And last but not least, delete the last subsystem
|
||||
saveddata_engine.execute("DELETE FROM modules WHERE ID = ?", (oldModules[4][0],))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# if something fails, fuck it, we tried. It'll default to the generic conversion below
|
||||
continue
|
||||
|
||||
@@ -6,16 +6,16 @@ Allow use of floats in damage pattern values
|
||||
|
||||
tmpTable = """
|
||||
CREATE TABLE "damagePatternsTemp" (
|
||||
"ID" INTEGER NOT NULL,
|
||||
"name" VARCHAR,
|
||||
"emAmount" FLOAT,
|
||||
"thermalAmount" FLOAT,
|
||||
"kineticAmount" FLOAT,
|
||||
"explosiveAmount" FLOAT,
|
||||
"ownerID" INTEGER,
|
||||
"created" DATETIME,
|
||||
"modified" DATETIME,
|
||||
PRIMARY KEY ("ID"),
|
||||
"ID" INTEGER NOT NULL,
|
||||
"name" VARCHAR,
|
||||
"emAmount" FLOAT,
|
||||
"thermalAmount" FLOAT,
|
||||
"kineticAmount" FLOAT,
|
||||
"explosiveAmount" FLOAT,
|
||||
"ownerID" INTEGER,
|
||||
"created" DATETIME,
|
||||
"modified" DATETIME,
|
||||
PRIMARY KEY ("ID"),
|
||||
FOREIGN KEY("ownerID") REFERENCES users ("ID")
|
||||
)
|
||||
"""
|
||||
|
||||
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))
|
||||
34
eos/db/migrations/upgrade39.py
Normal file
34
eos/db/migrations/upgrade39.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Migration 39
|
||||
|
||||
- Shield amplifier tiericide
|
||||
- CCP getting rid of DB TDs due to exploits
|
||||
"""
|
||||
|
||||
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
|
||||
),
|
||||
22933: ( # 'Investor' Tracking Disruptor I
|
||||
32416, # Dark Blood Tracking Disruptor
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
18
eos/db/migrations/upgrade40.py
Normal file
18
eos/db/migrations/upgrade40.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Migration 40
|
||||
|
||||
Imports all item conversions since Migration 28 and runs them against module.baseItemID. This column seems to have been
|
||||
forgotten about since it's been added.
|
||||
|
||||
"""
|
||||
from .upgrade36 import CONVERSIONS as u36
|
||||
from .upgrade37 import CONVERSIONS as u37
|
||||
from .upgrade38 import CONVERSIONS as u38
|
||||
from .upgrade39 import CONVERSIONS as u39
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
for conversions in [u36, u37, u38, u39]:
|
||||
for replacement_item, list in conversions.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
50
eos/db/migrations/upgrade41.py
Normal file
50
eos/db/migrations/upgrade41.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Migration 41
|
||||
|
||||
- Resistance plating tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16345: ( # Upgraded Layered Coating I
|
||||
16347, # Limited Layered Plating I
|
||||
16349, # 'Scarab' Layered Plating I
|
||||
16351, # 'Grail' Layered Plating I
|
||||
),
|
||||
16305: ( # Upgraded Multispectrum Coating I
|
||||
16307, # Limited Adaptive Nano Plating I
|
||||
16309, # 'Collateral' Adaptive Nano Plating I
|
||||
16311, # 'Refuge' Adaptive Nano Plating I
|
||||
),
|
||||
16329: ( # Upgraded EM Coating I
|
||||
16331, # Limited EM Plating I
|
||||
16333, # 'Contour' EM Plating I
|
||||
16335, # 'Spiegel' EM Plating I
|
||||
),
|
||||
16321: ( # Upgraded Explosive Coating I
|
||||
16323, # Limited Explosive Plating I
|
||||
16325, # Experimental Explosive Plating I
|
||||
16319, # 'Aegis' Explosive Plating I
|
||||
),
|
||||
16313: ( # Upgraded Kinetic Coating I
|
||||
16315, # Limited Kinetic Plating I
|
||||
16317, # Experimental Kinetic Plating I
|
||||
16327, # 'Element' Kinetic Plating I
|
||||
),
|
||||
16337: ( # Upgraded Thermal Coating I
|
||||
16339, # Limited Thermal Plating I
|
||||
16341, # Experimental Thermal Plating I
|
||||
16343, # Prototype Thermal Plating 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 "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
50
eos/db/migrations/upgrade42.py
Normal file
50
eos/db/migrations/upgrade42.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Migration 42
|
||||
|
||||
- Resistance membrane tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16391: ( # Compact Multispectrum Energized Membrane
|
||||
16389, # Experimental Energized Adaptive Nano Membrane I
|
||||
16387, # Limited Energized Adaptive Nano Membrane I
|
||||
16385, # Upgraded Energized Adaptive Nano Membrane I
|
||||
),
|
||||
16423: ( # Compact Layered Energized Membrane
|
||||
16421, # Experimental Energized Armor Layering Membrane I
|
||||
16419, # Limited Energized Armor Layering Membrane I
|
||||
16417, # Upgraded Energized Armor Layering Membrane I
|
||||
),
|
||||
16415: ( # Compact EM Energized Membrane
|
||||
16413, # Experimental Energized EM Membrane I
|
||||
16411, # Limited Energized EM Membrane I
|
||||
16409, # Upgraded Energized EM Membrane I
|
||||
),
|
||||
16407: ( # Compact Explosive Energized Membrane
|
||||
16405, # Experimental Energized Explosive Membrane I
|
||||
16403, # Limited Energized Explosive Membrane I
|
||||
16401, # Upgraded Energized Explosive Membrane I
|
||||
),
|
||||
16399: ( # Compact Kinetic Energized Membrane
|
||||
16397, # Experimental Energized Kinetic Membrane I
|
||||
16395, # Limited Energized Kinetic Membrane I
|
||||
16393, # Upgraded Energized Kinetic Membrane I
|
||||
),
|
||||
16431: ( # Compact Thermal Energized Membrane
|
||||
16429, # Experimental Energized Thermal Membrane I
|
||||
16427, # Limited Energized Thermal Membrane I
|
||||
16425, # Upgraded Energized Thermal Membrane 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 "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
26
eos/db/migrations/upgrade43.py
Normal file
26
eos/db/migrations/upgrade43.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Migration 43
|
||||
|
||||
- Shield booster amplifier tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
16533: ( # Stalwart Restrained Shield Boost Amplifier
|
||||
16531, # 5a Prototype Shield Support I
|
||||
),
|
||||
16535: ( # Copasetic Compact Shield Boost Amplifier
|
||||
16529, # Ionic Field Accelerator 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 "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
@@ -13,6 +13,5 @@ __all__ = [
|
||||
"miscData",
|
||||
"targetProfile",
|
||||
"override",
|
||||
"implantSet",
|
||||
"loadDefaultDatabaseValues"
|
||||
"implantSet"
|
||||
]
|
||||
|
||||
@@ -24,16 +24,20 @@ import datetime
|
||||
from eos.db import saveddata_meta
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
|
||||
damagePatterns_table = Table("damagePatterns", saveddata_meta,
|
||||
Column("ID", Integer, primary_key=True),
|
||||
Column("name", String),
|
||||
Column("emAmount", Float),
|
||||
Column("thermalAmount", Float),
|
||||
Column("kineticAmount", Float),
|
||||
Column("explosiveAmount", Float),
|
||||
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)
|
||||
)
|
||||
damagePatterns_table = Table(
|
||||
'damagePatterns',
|
||||
saveddata_meta,
|
||||
Column('ID', Integer, primary_key=True),
|
||||
Column('name', String),
|
||||
Column('emAmount', Float),
|
||||
Column('thermalAmount', Float),
|
||||
Column('kineticAmount', Float),
|
||||
Column('explosiveAmount', Float),
|
||||
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})
|
||||
|
||||
@@ -53,8 +53,10 @@ fits_table = Table("fits", saveddata_meta,
|
||||
Column("timestamp", Integer, nullable=False),
|
||||
Column("characterID", ForeignKey("characters.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("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
|
||||
Column("builtinTargetResistsID", Integer, nullable=True),
|
||||
Column("modeID", Integer, nullable=True),
|
||||
Column("implantLocation", Integer, nullable=False),
|
||||
Column("notes", String, nullable=True),
|
||||
@@ -233,8 +235,10 @@ mapper(es_Fit, fits_table,
|
||||
"_Fit__character": relation(
|
||||
Character,
|
||||
backref="fits"),
|
||||
"_Fit__damagePattern": relation(DamagePattern),
|
||||
"_Fit__targetProfile": relation(TargetProfile),
|
||||
"_Fit__userDamagePattern": relation(DamagePattern),
|
||||
"_Fit__builtinDamagePatternID": fits_table.c.builtinDamagePatternID,
|
||||
"_Fit__userTargetProfile": relation(TargetProfile),
|
||||
"_Fit__builtinTargetProfileID": fits_table.c.builtinTargetResistsID,
|
||||
"projectedOnto": projectedFitSourceRel,
|
||||
"victimOf": relationship(
|
||||
ProjectedFit,
|
||||
|
||||
@@ -1,248 +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", 1, 0, 0, 0],
|
||||
["[Generic]Thermal", 0, 1, 0, 0],
|
||||
["[Generic]Kinetic", 0, 0, 1, 0],
|
||||
["[Generic]Explosive", 0, 0, 0, 1],
|
||||
["[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", 7.7, 7.7, 0, 0],
|
||||
["[Frequency Crystals][T2] Scorch", 9, 2, 0, 0],
|
||||
["[Frequency Crystals][T2] Gleam", 7, 7, 0, 0],
|
||||
["[Frequency Crystals][T2] Aurora", 5, 3, 0, 0],
|
||||
["[Frequency Crystals]Multifrequency", 7, 5, 0, 0],
|
||||
["[Frequency Crystals]Gamma", 7, 4, 0, 0],
|
||||
["[Frequency Crystals]Xray", 6, 4, 0, 0],
|
||||
["[Frequency Crystals]Ultraviolet", 6, 3, 0, 0],
|
||||
["[Frequency Crystals]Standard", 5, 3, 0, 0],
|
||||
["[Frequency Crystals]Infrared", 5, 2, 0, 0],
|
||||
["[Frequency Crystals]Microwave", 4, 2, 0, 0],
|
||||
["[Frequency Crystals]Radio", 5, 0, 0, 0],
|
||||
["[Hybrid Charges][T2] Void", 0, 7.7, 7.7, 0],
|
||||
["[Hybrid Charges][T2] Null", 0, 6, 5, 0],
|
||||
["[Hybrid Charges][T2] Javelin", 0, 8, 6, 0],
|
||||
["[Hybrid Charges][T2] Spike", 0, 4, 4, 0],
|
||||
["[Hybrid Charges]Antimatter", 0, 5, 7, 0],
|
||||
["[Hybrid Charges]Plutonium", 0, 5, 6, 0],
|
||||
["[Hybrid Charges]Uranium", 0, 4, 6, 0],
|
||||
["[Hybrid Charges]Thorium", 0, 4, 5, 0],
|
||||
["[Hybrid Charges]Lead", 0, 3, 5, 0],
|
||||
["[Hybrid Charges]Iridium", 0, 3, 4, 0],
|
||||
["[Hybrid Charges]Tungsten", 0, 2, 4, 0],
|
||||
["[Hybrid Charges]Iron", 0, 2, 3, 0],
|
||||
["[Missiles]Mjolnir", 1, 0, 0, 0],
|
||||
["[Missiles]Inferno", 0, 1, 0, 0],
|
||||
["[Missiles]Scourge", 0, 0, 1, 0],
|
||||
["[Missiles]Nova", 0, 0, 0, 1],
|
||||
["[Missiles][Structure] Standup Missile", 1, 1, 1, 1],
|
||||
["[Projectile Ammo][T2] Hail", 0, 0, 3.3, 12.1],
|
||||
["[Projectile Ammo][T2] Barrage", 0, 0, 5, 6],
|
||||
["[Projectile Ammo][T2] Quake", 0, 0, 5, 9],
|
||||
["[Projectile Ammo][T2] Tremor", 0, 0, 3, 5],
|
||||
["[Projectile Ammo]EMP", 9, 0, 1, 2],
|
||||
["[Projectile Ammo]Phased Plasma", 0, 10, 2, 0],
|
||||
["[Projectile Ammo]Fusion", 0, 0, 2, 10],
|
||||
["[Projectile Ammo]Depleted Uranium", 0, 3, 2, 3],
|
||||
["[Projectile Ammo]Titanium Sabot", 0, 0, 6, 2],
|
||||
["[Projectile Ammo]Proton", 3, 0, 2, 0],
|
||||
["[Projectile Ammo]Carbonized Lead", 0, 0, 4, 1],
|
||||
["[Projectile Ammo]Nuclear", 0, 0, 1, 4],
|
||||
# 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.add(damageProfile)
|
||||
else:
|
||||
damageProfile.emAmount = em
|
||||
damageProfile.thermalAmount = therm
|
||||
damageProfile.kineticAmount = kin
|
||||
damageProfile.explosiveAmount = exp
|
||||
eos.db.commit()
|
||||
|
||||
@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.getTargetProfile(name)
|
||||
if targetProfile is None:
|
||||
targetProfile = es_TargetProfile(em, therm, kin, exp, maxVel, sigRad, radius)
|
||||
targetProfile.name = name
|
||||
eos.db.add(targetProfile)
|
||||
else:
|
||||
targetProfile.emAmount = em
|
||||
targetProfile.thermalAmount = therm
|
||||
targetProfile.kineticAmount = kin
|
||||
targetProfile.explosiveAmount = exp
|
||||
targetProfile.maxVelocity = maxVel
|
||||
targetProfile.signatureRadius = sigRad
|
||||
targetProfile.radius = radius
|
||||
eos.db.commit()
|
||||
|
||||
|
||||
@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.add(damageProfile)
|
||||
else:
|
||||
damageProfile.emAmount = em
|
||||
damageProfile.thermalAmount = therm
|
||||
damageProfile.kineticAmount = kin
|
||||
damageProfile.explosiveAmount = exp
|
||||
eos.db.commit()
|
||||
@@ -413,7 +413,7 @@ def getDamagePattern(lookfor, eager=None):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
pattern = saveddata_session.query(DamagePattern).options(*eager).filter(
|
||||
DamagePattern.name == lookfor).first()
|
||||
DamagePattern.rawName == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return pattern
|
||||
@@ -434,7 +434,7 @@ def getTargetProfile(lookfor, eager=None):
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
pattern = saveddata_session.query(TargetProfile).options(*eager).filter(
|
||||
TargetProfile.name == lookfor).first()
|
||||
TargetProfile.rawName == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return pattern
|
||||
@@ -470,7 +470,7 @@ def searchFits(nameLike, where=None, eager=None):
|
||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||
eager = processEager(eager)
|
||||
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
|
||||
|
||||
@@ -560,6 +560,8 @@ def commit():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.commit()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
@@ -570,6 +572,8 @@ def flush():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
@@ -24,23 +24,28 @@ import datetime
|
||||
from eos.db import saveddata_meta
|
||||
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,
|
||||
properties={
|
||||
"_maxVelocity": targetProfiles_table.c.maxVelocity,
|
||||
"_signatureRadius": targetProfiles_table.c.signatureRadius,
|
||||
"_radius": targetProfiles_table.c.radius})
|
||||
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,
|
||||
properties={
|
||||
'rawName': targetProfiles_table.c.name,
|
||||
'_maxVelocity': targetProfiles_table.c.maxVelocity,
|
||||
'_signatureRadius': targetProfiles_table.c.signatureRadius,
|
||||
'_radius': targetProfiles_table.c.radius})
|
||||
|
||||
1654
eos/effects.py
1654
eos/effects.py
File diff suppressed because it is too large
Load Diff
110
eos/gamedata.py
110
eos/gamedata.py
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from logbook import Logger
|
||||
@@ -145,6 +146,12 @@ class Effect(EqBase):
|
||||
|
||||
return self.__effectDef is not None
|
||||
|
||||
@property
|
||||
def dealsDamage(self):
|
||||
if not self.__generated:
|
||||
self.__generateHandler()
|
||||
return self.__dealsDamage
|
||||
|
||||
def isType(self, type):
|
||||
"""
|
||||
Check if this effect is of the passed type
|
||||
@@ -166,6 +173,7 @@ class Effect(EqBase):
|
||||
self.__handler = getattr(effectDef, "handler", eos.effects.BaseEffect.handler)
|
||||
self.__runTime = getattr(effectDef, "runTime", "normal")
|
||||
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
|
||||
self.__dealsDamage = effectDef.dealsDamage
|
||||
effectType = getattr(effectDef, "type", None)
|
||||
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
|
||||
self.__type = effectType
|
||||
@@ -174,6 +182,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.debug("ImportError generating handler: {0}", e)
|
||||
except AttributeError as e:
|
||||
@@ -181,12 +190,16 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.error("AttributeError generating handler: {0}", e)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.critical("Exception generating handler:")
|
||||
pyfalog.critical(e)
|
||||
@@ -199,45 +212,20 @@ class Effect(EqBase):
|
||||
|
||||
try:
|
||||
return self.__effectDef.get(key, None)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return getattr(self.__effectDef, key, None)
|
||||
|
||||
|
||||
class Item(EqBase):
|
||||
MOVE_ATTRS = (4, # Mass
|
||||
38, # Capacity
|
||||
161) # Volume
|
||||
|
||||
MOVE_ATTR_INFO = 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
|
||||
def init(self):
|
||||
self.__race = None
|
||||
self.__requiredSkills = None
|
||||
self.__requiredFor = None
|
||||
self.__moved = False
|
||||
self.__offensive = None
|
||||
self.__assistive = None
|
||||
self.__overrides = None
|
||||
@@ -259,9 +247,6 @@ class Item(EqBase):
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
if not self.__moved:
|
||||
self.moveAttrs()
|
||||
|
||||
return self.__attributes
|
||||
|
||||
@property
|
||||
@@ -314,50 +299,26 @@ class Item(EqBase):
|
||||
eos.db.saveddata_session.delete(override)
|
||||
eos.db.commit()
|
||||
|
||||
srqIDMap = {182: 277, 183: 278, 184: 279, 1285: 1286, 1289: 1287, 1290: 1288}
|
||||
|
||||
@property
|
||||
def requiredSkills(self):
|
||||
if self.__requiredSkills is None:
|
||||
requiredSkills = OrderedDict()
|
||||
self.__requiredSkills = requiredSkills
|
||||
# Map containing attribute IDs we may need for required skills
|
||||
# { requiredSkillX : requiredSkillXLevel }
|
||||
combinedAttrIDs = set(self.srqIDMap.keys()).union(set(self.srqIDMap.values()))
|
||||
# Map containing result of the request
|
||||
# { 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
|
||||
self.__requiredSkills = {}
|
||||
if self.reqskills:
|
||||
for skillTypeID, skillLevel in json.loads(self.reqskills).items():
|
||||
skillItem = eos.db.getItem(int(skillTypeID))
|
||||
if skillItem:
|
||||
self.__requiredSkills[skillItem] = skillLevel
|
||||
return self.__requiredSkills
|
||||
|
||||
@property
|
||||
def requiredFor(self):
|
||||
if self.__requiredFor is None:
|
||||
self.__requiredFor = dict()
|
||||
|
||||
# Map containing attribute IDs we may need for required skills
|
||||
|
||||
# Get relevant attribute values from db (required skill IDs and levels) for our item
|
||||
q = eos.db.getRequiredFor(self.ID, self.srqIDMap)
|
||||
|
||||
for itemID, lvl in q:
|
||||
# Fetch item from database and fill map
|
||||
item = eos.db.getItem(itemID)
|
||||
self.__requiredFor[item] = lvl
|
||||
|
||||
self.__requiredFor = {}
|
||||
if self.requiredfor:
|
||||
for typeID, skillLevel in json.loads(self.requiredfor).items():
|
||||
requiredForItem = eos.db.getItem(int(typeID))
|
||||
if requiredForItem:
|
||||
self.__requiredFor[requiredForItem] = skillLevel
|
||||
return self.__requiredFor
|
||||
|
||||
factionMap = {
|
||||
@@ -382,7 +343,11 @@ class Item(EqBase):
|
||||
if self.__race is None:
|
||||
|
||||
try:
|
||||
if self.category.categoryName == 'Structure':
|
||||
if (
|
||||
self.category.categoryName == 'Structure' or
|
||||
# Here until CCP puts their shit together
|
||||
self.name in ("Thunderchild", "Stormbringer", "Skybreaker")
|
||||
):
|
||||
self.__race = "upwell"
|
||||
else:
|
||||
self.__race = self.factionMap[self.factionID]
|
||||
@@ -400,6 +365,7 @@ class Item(EqBase):
|
||||
9 : "guristas", # Caldari + Gallente
|
||||
10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills
|
||||
12 : "sisters", # Amarr + Gallente
|
||||
15 : "concord",
|
||||
16 : "jove",
|
||||
32 : "sansha", # Incrusion Sansha
|
||||
128: "ore",
|
||||
@@ -741,5 +707,15 @@ class Unit(EqBase):
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Traits(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class ImplantSet(EqBase):
|
||||
|
||||
@property
|
||||
def fullName(self):
|
||||
if not self.gradeName:
|
||||
return self.setName
|
||||
return '{} {}'.format(self.gradeName, self.setName)
|
||||
|
||||
@@ -21,6 +21,9 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.round import roundToPrec
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@@ -56,9 +59,8 @@ class BoosterSideEffect:
|
||||
@property
|
||||
def name(self):
|
||||
return "{0}% {1}".format(
|
||||
self.booster.getModifiedItemAttr(self.attr),
|
||||
self.__effect.getattr('displayName') or self.__effect.name,
|
||||
)
|
||||
roundToPrec(self.booster.getModifiedItemAttr(self.attr), 5),
|
||||
self.__effect.getattr('displayName') or self.__effect.name)
|
||||
|
||||
@property
|
||||
def attr(self):
|
||||
|
||||
@@ -18,21 +18,174 @@
|
||||
# ===============================================================================
|
||||
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
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:
|
||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
||||
|
||||
DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
|
||||
_builtins = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.builtin = False
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.builtin = False
|
||||
|
||||
def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
|
||||
self.emAmount = emAmount
|
||||
self.thermalAmount = thermalAmount
|
||||
self.kineticAmount = kineticAmount
|
||||
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):
|
||||
ehp = {}
|
||||
for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
|
||||
@@ -98,7 +251,7 @@ class DamagePattern:
|
||||
lookup = {}
|
||||
current = eos.db.getDamagePatternList()
|
||||
for pattern in current:
|
||||
lookup[pattern.name] = pattern
|
||||
lookup[pattern.rawName] = pattern
|
||||
|
||||
for line in lines:
|
||||
try:
|
||||
@@ -107,6 +260,8 @@ class DamagePattern:
|
||||
line = line.split('#', 1)[0] # allows for comments
|
||||
type, data = line.rsplit('=', 1)
|
||||
type, data = type.strip(), data.split(',')
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# Data isn't in correct format, continue to next line
|
||||
continue
|
||||
@@ -121,6 +276,8 @@ class DamagePattern:
|
||||
for index, val in enumerate(data):
|
||||
try:
|
||||
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
continue
|
||||
|
||||
@@ -131,7 +288,7 @@ class DamagePattern:
|
||||
eos.db.save(pattern)
|
||||
else:
|
||||
pattern = DamagePattern(**fields)
|
||||
pattern.name = name.strip()
|
||||
pattern.rawName = name.strip()
|
||||
eos.db.save(pattern)
|
||||
patterns.append(pattern)
|
||||
|
||||
@@ -147,11 +304,41 @@ class DamagePattern:
|
||||
out += "# Values are in following format:\n"
|
||||
out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
|
||||
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()
|
||||
|
||||
@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):
|
||||
p = DamagePattern(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
|
||||
p.name = "%s copy" % self.name
|
||||
p.rawName = "%s copy" % self.rawName
|
||||
return p
|
||||
|
||||
@@ -364,3 +364,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.item.groupID in fitDroneGroupLimits:
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (ignoreState or self.amountActive > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -99,7 +99,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__itemModifiedAttributes = 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 = []
|
||||
for ability in self.__getAbilities():
|
||||
self.__abilities.append(ability)
|
||||
@@ -441,3 +441,15 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def canDealDamage(self, ignoreState=False, ignoreAbilityState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
if not self.active and not ignoreState:
|
||||
return False
|
||||
for ability in self.abilities:
|
||||
if not ability.active and not ignoreAbilityState:
|
||||
continue
|
||||
if ability.effect.dealsDamage:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -21,22 +21,25 @@ import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import log, sqrt
|
||||
from math import floor, log, sqrt
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos import capSim
|
||||
from eos.calc import calculateMultiplier, calculateLockTime
|
||||
from eos.calc import calculateLockTime, calculateMultiplier
|
||||
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
|
||||
from eos.effectHandlerHelpers import (
|
||||
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
|
||||
HandledModuleList, HandledProjectedDroneList, HandledProjectedModList)
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
|
||||
|
||||
@@ -163,14 +166,29 @@ class Fit:
|
||||
self.__capUsed = None
|
||||
self.__capRecharge = None
|
||||
self.__savedCapSimData.clear()
|
||||
# Ancillary tank modules affect this
|
||||
self.__sustainableTank = None
|
||||
self.__effectiveSustainableTank = None
|
||||
|
||||
@property
|
||||
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
|
||||
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.__weaponVolleyMap = {}
|
||||
self.__droneDps = None
|
||||
@@ -178,11 +196,25 @@ class Fit:
|
||||
|
||||
@property
|
||||
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
|
||||
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.__effectiveTank = None
|
||||
|
||||
@@ -347,8 +379,9 @@ class Fit:
|
||||
|
||||
@property
|
||||
def maxTargets(self):
|
||||
return min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
||||
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
||||
maxTargets = min(self.extraAttributes["maxTargetsLockedFromSkills"],
|
||||
self.ship.getModifiedItemAttr("maxLockedTargets"))
|
||||
return floor(floatUnerr(maxTargets))
|
||||
|
||||
@property
|
||||
def maxTargetRange(self):
|
||||
@@ -545,11 +578,15 @@ class Fit:
|
||||
|
||||
if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
||||
"Shield Emission Systems"), "capacitorNeed", value)
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||
mod.item.requiresSkill("Shield Emission Systems") or
|
||||
mod.item.requiresSkill("Capital Shield Emission Systems"),
|
||||
"capacitorNeed", value)
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
||||
"Shield Emission Systems"), "duration", value)
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||
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
|
||||
self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True)
|
||||
@@ -559,12 +596,16 @@ class Fit:
|
||||
self.ship.boostItemAttr("armor%sDamageResonance" % damageType, value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems"),
|
||||
"capacitorNeed", value)
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems"),
|
||||
"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"),
|
||||
"capacitorNeed", 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
|
||||
self.ship.boostItemAttr("armorHP", value, stackingPenalties=True)
|
||||
@@ -711,14 +752,14 @@ class Fit:
|
||||
|
||||
if warfareBuffID == 79: # AOE_Beacon_bioluminescence_cloud
|
||||
self.ship.boostItemAttr("signatureRadius", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"signatureRadius", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 80: # AOE_Beacon_caustic_cloud_local_repair
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Systems"),
|
||||
"armorDamageAmount", value, stackingPenalties=True)
|
||||
if warfareBuffID == 80: # AOE_Beacon_caustic_cloud_inertia
|
||||
self.ship.boostItemAttr("agility", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 81: # AOE_Beacon_caustic_cloud_remote_repair
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"),
|
||||
"armorDamageAmount", value, stackingPenalties=True)
|
||||
if warfareBuffID == 81: # AOE_Beacon_caustic_cloud_velocity
|
||||
self.ship.boostItemAttr("maxVelocity", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 88: # AOE_Beacon_filament_cloud_shield_booster_shield_bonus
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation"),
|
||||
@@ -733,7 +774,11 @@ class Fit:
|
||||
if warfareBuffID == 90: # Weather_electric_storm_EM_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}EmDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}EmDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("emDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"emDamageResonance", value) #for hull
|
||||
|
||||
if warfareBuffID == 92: # Weather_electric_storm_capacitor_recharge_bonus
|
||||
self.ship.boostItemAttr("rechargeRate", value, stackingPenalties=True)
|
||||
@@ -741,32 +786,54 @@ class Fit:
|
||||
if warfareBuffID == 93: # Weather_xenon_gas_explosive_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}ExplosiveDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}ExplosiveDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("explosiveDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"explosiveDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 94: # Weather_xenon_gas_shield_hp_bonus
|
||||
self.ship.boostItemAttr("shieldCapacity", value) # for hull
|
||||
self.ship.boostItemAttr("shieldCapacity", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"shieldCapacity", value)
|
||||
|
||||
if warfareBuffID == 95: # Weather_infernal_thermal_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}ThermalDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}ThermalDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("thermalDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"thermalDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 96: # Weather_infernal_armor_hp_bonus
|
||||
self.ship.boostItemAttr("armorHP", value) # for hull
|
||||
self.ship.boostItemAttr("armorHP", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"armorHP", value)
|
||||
|
||||
if warfareBuffID == 97: # Weather_darkness_turret_range_penalty
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"maxRange", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"maxRange", value, stackingPenalties=True)
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
|
||||
"falloff", value, stackingPenalties=True)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"falloff", value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 98: # Weather_darkness_velocity_bonus
|
||||
self.ship.boostItemAttr("maxVelocity", value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"maxVelocity", value)
|
||||
|
||||
if warfareBuffID == 99: # Weather_caustic_toxin_kinetic_resistance_penalty
|
||||
for tankType in ("shield", "armor"):
|
||||
self.ship.boostItemAttr("{}KineticDamageResonance".format(tankType), value)
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"{}KineticDamageResonance".format(tankType), value)
|
||||
self.ship.boostItemAttr("kineticDamageResonance", value) # for hull
|
||||
self.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"),
|
||||
"kineticDamageResonance", value) # for hull
|
||||
|
||||
if warfareBuffID == 100: # Weather_caustic_toxin_scan_resolution_bonus
|
||||
self.ship.boostItemAttr("scanResolution", value, stackingPenalties=True)
|
||||
@@ -987,6 +1054,16 @@ class Fit:
|
||||
if mod.isEmpty:
|
||||
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
|
||||
def modCount(self):
|
||||
x = 0
|
||||
@@ -1096,7 +1173,7 @@ class Fit:
|
||||
def droneBayUsed(self):
|
||||
amount = 0
|
||||
for d in self.drones:
|
||||
amount += d.item.volume * d.amount
|
||||
amount += d.item.attributes['volume'].value * d.amount
|
||||
|
||||
return amount
|
||||
|
||||
@@ -1104,7 +1181,7 @@ class Fit:
|
||||
def fighterBayUsed(self):
|
||||
amount = 0
|
||||
for f in self.fighters:
|
||||
amount += f.item.volume * f.amount
|
||||
amount += f.item.attributes['volume'].value * f.amount
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
@@ -64,7 +64,9 @@ ProjectedSystem = {
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
||||
SYSTEM_GROUPS = (
|
||||
"Effect Beacon", "MassiveEnvironments", "Abyssal Hazards",
|
||||
"Non-Interactable Object", "Destructible Effect Beacon")
|
||||
|
||||
def __init__(self, item, baseItem=None, mutaplasmid=None):
|
||||
"""Initialize a module from the program"""
|
||||
@@ -196,11 +198,18 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# todo: validate baseItem as well if it's set.
|
||||
if self.isEmpty:
|
||||
return False
|
||||
return (
|
||||
self.__item is None or (
|
||||
self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
||||
self.__item.group.name not in self.SYSTEM_GROUPS) or
|
||||
(self.item.isAbyssal and not self.isMutated))
|
||||
if self.__item is None:
|
||||
return True
|
||||
if (
|
||||
self.__item.category.name not in ("Module", "Subsystem", "Structure Module")
|
||||
and self.__item.group.name not in self.SYSTEM_GROUPS
|
||||
):
|
||||
return True
|
||||
if self.item.isAbyssal and not self.isMutated:
|
||||
return True
|
||||
if self.isMutated and not self.__mutaplasmid:
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def isMutated(self):
|
||||
@@ -214,8 +223,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if charge is None:
|
||||
charges = 0
|
||||
else:
|
||||
chargeVolume = charge.volume
|
||||
containerCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
containerCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
else:
|
||||
@@ -265,7 +274,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def isExclusiveSystemEffect(self):
|
||||
return self.item.group.name in ("Effect Beacon", "Non-Interactable Object", "MassiveEnvironments")
|
||||
# See issue #2258
|
||||
# return self.item.group.name in ("Effect Beacon", "Non-Interactable Object", "MassiveEnvironments")
|
||||
return False
|
||||
|
||||
@property
|
||||
def isCapitalSize(self):
|
||||
@@ -461,6 +472,20 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.isEmpty:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (
|
||||
ignoreState or
|
||||
effect.isType('offline') or
|
||||
(effect.isType('passive') and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType('active') and self.state >= FittingModuleState.ACTIVE) or
|
||||
(effect.isType('overheat') and self.state >= FittingModuleState.OVERHEATED)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
@@ -696,7 +721,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
# Check this only if we're told to do so
|
||||
if hardpointLimit:
|
||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
||||
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -712,6 +737,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
||||
return False
|
||||
# Some destructible effect beacons contain active effects, hardcap those at online state
|
||||
elif state > FittingModuleState.ONLINE and self.slot == FittingSlot.SYSTEM:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -778,8 +806,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# Check sizes, if 'charge size > module volume' it won't fit
|
||||
if charge is None:
|
||||
return True
|
||||
chargeVolume = charge.volume
|
||||
moduleCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
moduleCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
||||
return False
|
||||
|
||||
@@ -1037,7 +1065,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
elif click == "ctrl":
|
||||
state = FittingModuleState.OFFLINE
|
||||
else:
|
||||
state = transitionMap[currState]
|
||||
try:
|
||||
state = transitionMap[currState]
|
||||
except KeyError:
|
||||
state = min(transitionMap)
|
||||
# If passive module tries to transition into online and fails,
|
||||
# put it to passive instead
|
||||
if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
|
||||
|
||||
@@ -73,12 +73,16 @@ class Mutator(EqBase):
|
||||
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)
|
||||
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
self.module = None
|
||||
|
||||
@validates("value")
|
||||
def validator(self, key, val):
|
||||
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||
if self.baseValue == 0:
|
||||
return 0
|
||||
mod = val / self.baseValue
|
||||
|
||||
if self.minMod <= mod <= self.maxMod:
|
||||
@@ -116,15 +120,24 @@ class Mutator(EqBase):
|
||||
|
||||
@property
|
||||
def baseValue(self):
|
||||
return self.baseAttribute.value
|
||||
try:
|
||||
return self.baseAttribute.value
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def minValue(self):
|
||||
return self.minMod * self.baseAttribute.value
|
||||
try:
|
||||
return self.minMod * self.baseAttribute.value
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def maxValue(self):
|
||||
return self.maxMod * self.baseAttribute.value
|
||||
try:
|
||||
return self.maxMod * self.baseAttribute.value
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
@property
|
||||
def attribute(self):
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
|
||||
import math
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
import eos.db
|
||||
|
||||
@@ -28,13 +30,173 @@ import eos.db
|
||||
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.303, 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:
|
||||
|
||||
# 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):
|
||||
self.builtin = False
|
||||
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):
|
||||
self.emAmount = emAmount
|
||||
self.thermalAmount = thermalAmount
|
||||
@@ -44,7 +206,29 @@ class TargetProfile:
|
||||
self._signatureRadius = signatureRadius
|
||||
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
|
||||
def getIdeal(cls):
|
||||
@@ -57,8 +241,9 @@ class TargetProfile:
|
||||
maxVelocity=0,
|
||||
signatureRadius=None,
|
||||
radius=0)
|
||||
cls._idealTarget.name = 'Ideal Target'
|
||||
cls._idealTarget.ID = -1
|
||||
cls._idealTarget.rawName = 'Ideal Target'
|
||||
cls._idealTarget.ID = 0
|
||||
cls._idealTarget.builtin = True
|
||||
return cls._idealTarget
|
||||
|
||||
@property
|
||||
@@ -100,7 +285,7 @@ class TargetProfile:
|
||||
lookup = {}
|
||||
current = eos.db.getTargetProfileList()
|
||||
for pattern in current:
|
||||
lookup[pattern.name] = pattern
|
||||
lookup[pattern.rawName] = pattern
|
||||
|
||||
for line in lines:
|
||||
try:
|
||||
@@ -109,6 +294,8 @@ class TargetProfile:
|
||||
line = line.split('#', 1)[0] # allows for comments
|
||||
type, data = line.rsplit('=', 1)
|
||||
type, data = type.strip(), [d.strip() for d in data.split(',')]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pyfalog.warning("Data isn't in correct format, continue to next line.")
|
||||
continue
|
||||
@@ -127,6 +314,8 @@ class TargetProfile:
|
||||
try:
|
||||
assert 0 <= val <= 100
|
||||
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val / 100
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pyfalog.warning("Caught unhandled exception in import patterns.")
|
||||
continue
|
||||
@@ -149,7 +338,7 @@ class TargetProfile:
|
||||
eos.db.save(pattern)
|
||||
else:
|
||||
pattern = TargetProfile(**fields)
|
||||
pattern.name = name.strip()
|
||||
pattern.rawName = name.strip()
|
||||
eos.db.save(pattern)
|
||||
patterns.append(pattern)
|
||||
|
||||
@@ -166,7 +355,7 @@ class TargetProfile:
|
||||
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %],[Max velocity m/s],[Signature radius m],[Radius m]\n\n"
|
||||
for dp in patterns:
|
||||
out += cls.EXPORT_FORMAT % (
|
||||
dp.name,
|
||||
dp.rawName,
|
||||
dp.emAmount * 100,
|
||||
dp.thermalAmount * 100,
|
||||
dp.kineticAmount * 100,
|
||||
@@ -178,9 +367,39 @@ class TargetProfile:
|
||||
|
||||
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):
|
||||
p = TargetProfile(
|
||||
self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount,
|
||||
self._maxVelocity, self._signatureRadius, self._radius)
|
||||
p.name = "%s copy" % self.name
|
||||
p.rawName = "%s copy" % self.rawName
|
||||
return p
|
||||
|
||||
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)
|
||||
@@ -26,11 +26,12 @@ VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle'
|
||||
|
||||
class YDef:
|
||||
|
||||
def __init__(self, handle, unit, label, selectorLabel=None):
|
||||
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
|
||||
self.handle = handle
|
||||
self.unit = unit
|
||||
self.label = label
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
@@ -53,12 +54,13 @@ class YDef:
|
||||
|
||||
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.unit = unit
|
||||
self.label = label
|
||||
self.mainInput = mainInput
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
|
||||
@@ -37,7 +37,14 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
||||
if "ChainLightning" in mod.item.effects:
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getVortonMult(
|
||||
mod=mod,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
elif mod.hardpoint == FittingHardpoint.TURRET:
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
@@ -56,7 +63,6 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
@@ -151,7 +157,21 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
|
||||
return mult
|
||||
|
||||
|
||||
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
|
||||
def getVortonMult(mod, distance, tgtSpeed, tgtSigRadius):
|
||||
rangeFactor = calculateRangeFactor(
|
||||
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
|
||||
|
||||
@@ -31,8 +31,8 @@ from .getter import (
|
||||
|
||||
class FitDamageStatsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
self._projectedCache = ProjectedDataCache()
|
||||
|
||||
|
||||
@@ -23,24 +23,6 @@ import math
|
||||
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):
|
||||
|
||||
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 = distance_t - distance_0
|
||||
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 .getter import Time2SpeedGetter, Time2DistanceGetter
|
||||
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
|
||||
|
||||
|
||||
class FitMobilityGraph(FitGraph):
|
||||
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
|
||||
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
||||
yDefs = [
|
||||
YDef(handle='speed', unit='m/s', label='Speed'),
|
||||
YDef(handle='distance', unit='km', label='Distance')]
|
||||
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
|
||||
YDef(handle='distance', unit='km', label='Distance'),
|
||||
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')
|
||||
|
||||
# Calculation stuff
|
||||
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
|
||||
_getters = {
|
||||
('time', 'speed'): Time2SpeedGetter,
|
||||
('time', 'distance'): Time2DistanceGetter}
|
||||
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
|
||||
('time', 'distance'): Time2DistanceGetter,
|
||||
('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}
|
||||
|
||||
@@ -26,8 +26,8 @@ from .getter import Distance2RpsGetter, Distance2RepAmountGetter, Time2RpsGetter
|
||||
|
||||
class FitRemoteRepsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -27,6 +27,10 @@ from .getter import (
|
||||
|
||||
class FitShieldRegenGraph(FitGraph):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||
|
||||
# UI stuff
|
||||
internalName = 'shieldRegenGraph'
|
||||
name = 'Shield Regeneration'
|
||||
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
|
||||
('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'),
|
||||
('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):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._subspeedCache = SubwarpSpeedCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -54,6 +54,8 @@ try:
|
||||
except ImportError as e:
|
||||
pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.')
|
||||
graphFrame_enabled = False
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
# We can get exceptions deep within matplotlib. Catch those. See GH #1046
|
||||
tb = traceback.format_exc()
|
||||
@@ -71,6 +73,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
# Remove matplotlib font cache, see #234
|
||||
try:
|
||||
cache_dir = mpl._get_cachedir()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib'))
|
||||
cache_file = os.path.join(cache_dir, 'fontList.cache')
|
||||
@@ -168,6 +172,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
legendData.append((color, lineStyle, source.shortName))
|
||||
else:
|
||||
legendData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName)))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
pyfalog.warning('Failed to plot "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
||||
self.canvas.draw()
|
||||
@@ -241,6 +247,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
src=source,
|
||||
tgt=target)
|
||||
addYMark(y)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
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
|
||||
|
||||
@@ -235,7 +235,7 @@ class GraphControlPanel(wx.Panel):
|
||||
fieldTextBox = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue))
|
||||
fieldTextBox.Bind(wx.EVT_TEXT, self.OnNonMainInputChanged)
|
||||
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
|
||||
if inputDef.iconID is not None:
|
||||
icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons')
|
||||
@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
|
||||
|
||||
self.ySubSelection.Clear()
|
||||
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.Enable(len(view.yDefs) > 1)
|
||||
self.ySubSelection.SetSelection(selectedY)
|
||||
|
||||
self.xSubSelection.Clear()
|
||||
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.Enable(len(view.xDefs) > 1)
|
||||
self.xSubSelection.SetSelection(selectedX)
|
||||
|
||||
@@ -27,7 +27,7 @@ import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from graphs.data.base import FitGraph
|
||||
from graphs.events import RESIST_MODE_CHANGED
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from service.settings import GraphSettings
|
||||
@@ -50,6 +50,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.includeHidden = includeHidden
|
||||
|
||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||
|
||||
@@ -74,7 +75,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
# Setup - graph selector
|
||||
for view in FitGraph.views:
|
||||
if view.hidden and not includeHidden:
|
||||
if view.hidden and not self.includeHidden:
|
||||
continue
|
||||
self.graphSelection.Append(view.name, view())
|
||||
self.graphSelection.SetSelection(0)
|
||||
|
||||
@@ -36,9 +36,9 @@ class VectorPicker(wx.Window):
|
||||
self._labelpos = int(kwargs.pop('labelpos', 0))
|
||||
self._offset = float(kwargs.pop('offset', 0))
|
||||
self._size = max(0, float(kwargs.pop('size', 50)))
|
||||
self._fontsize = max(1, float(kwargs.pop('fontsize', 8)))
|
||||
self._directionOnly = kwargs.pop('directionOnly', False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self._fontsize = max(1, float(kwargs.pop('fontsize', 8 / self.GetContentScaleFactor())))
|
||||
self._font = wx.Font(self._fontsize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
|
||||
self._angle = 0
|
||||
self.__length = 1
|
||||
@@ -107,8 +107,11 @@ class VectorPicker(wx.Window):
|
||||
dc = wx.BufferedPaintDC(self)
|
||||
self.Draw(dc)
|
||||
|
||||
def GetScaledClientSize(self):
|
||||
return tuple([dim / self.GetContentScaleFactor() for dim in self.GetClientSize()])
|
||||
|
||||
def Draw(self, dc):
|
||||
width, height = self.GetClientSize()
|
||||
width, height = self.GetScaledClientSize()
|
||||
if not width or not height:
|
||||
return
|
||||
dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID))
|
||||
@@ -122,9 +125,11 @@ class VectorPicker(wx.Window):
|
||||
a = math.radians(self._angle + self._offset)
|
||||
x = math.cos(a) * radius
|
||||
y = math.sin(a) * radius
|
||||
# See PR #2260 on why this is needed
|
||||
pointRadius = 2 / self.GetContentScaleFactor() if 'wxGTK' in wx.PlatformInfo else 2
|
||||
dc.DrawLine(radius + 2, radius + 2, radius + 2 + x * self._length, radius + 2 - y * self._length)
|
||||
dc.SetBrush(wx.BLACK_BRUSH)
|
||||
dc.DrawCircle(radius + 2 + x * self._length, radius + 2 - y * self._length, 2)
|
||||
dc.DrawCircle(radius + 2 + x * self._length, radius + 2 - y * self._length, pointRadius)
|
||||
|
||||
if self._label:
|
||||
labelText = self._label
|
||||
|
||||
@@ -43,7 +43,7 @@ class BaseWrapper:
|
||||
if self.isFit:
|
||||
return '{} ({})'.format(self.item.name, self.item.ship.item.name)
|
||||
elif self.isProfile:
|
||||
return self.item.name
|
||||
return self.item.fullName
|
||||
return ''
|
||||
|
||||
@property
|
||||
@@ -51,7 +51,7 @@ class BaseWrapper:
|
||||
if self.isFit:
|
||||
return '{} ({})'.format(self.item.name, self.item.ship.item.getShortName())
|
||||
elif self.isProfile:
|
||||
return self.item.name
|
||||
return self.item.shortName
|
||||
return ''
|
||||
|
||||
def getMaxVelocity(self, extraMultipliers=None, ignoreAfflictors=()):
|
||||
|
||||
@@ -22,12 +22,14 @@
|
||||
import wx
|
||||
|
||||
|
||||
class AuxiliaryFrame(wx.Frame):
|
||||
class AuxiliaryMixin:
|
||||
|
||||
_instance = 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 = {
|
||||
@@ -53,14 +55,26 @@ class AuxiliaryFrame(wx.Frame):
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
@classmethod
|
||||
def openOne(cls, parent, *args, **kwargs):
|
||||
def openOne(cls, parent, *args, forceReopen=False, **kwargs):
|
||||
"""If window is open and alive - raise it, open otherwise"""
|
||||
if not cls._instance:
|
||||
if not cls._instance or forceReopen:
|
||||
if cls._instance:
|
||||
cls._instance.Close()
|
||||
frame = cls(parent, *args, **kwargs)
|
||||
cls._instance = frame
|
||||
frame.Show()
|
||||
else:
|
||||
cls._instance.Raise()
|
||||
return cls._instance
|
||||
|
||||
|
||||
def OnSuppressedAction(self, event):
|
||||
return
|
||||
|
||||
|
||||
class AuxiliaryFrame(AuxiliaryMixin, wx.Frame):
|
||||
pass
|
||||
|
||||
|
||||
class AuxiliaryDialog(AuxiliaryMixin, wx.Dialog):
|
||||
pass
|
||||
@@ -24,6 +24,7 @@ import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
@@ -58,6 +59,7 @@ class CargoView(d.Display):
|
||||
self.lastFitId = None
|
||||
|
||||
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_KEY_UP, self.kbEvent)
|
||||
|
||||
@@ -66,6 +68,31 @@ class CargoView(d.Display):
|
||||
|
||||
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):
|
||||
"""
|
||||
Handles dragging of items from various pyfa displays which support it
|
||||
|
||||
@@ -36,6 +36,11 @@ import gui.fitCommands as cmd
|
||||
from gui.fitCommands.helpers import droneStackLimit
|
||||
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
|
||||
class DroneViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(DroneViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -186,17 +191,13 @@ class DroneView(Display):
|
||||
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
||||
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
def droneKey(self, drone):
|
||||
@staticmethod
|
||||
def droneKey(drone):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
groupName = sMkt.getMarketGroupByItem(drone.item).name
|
||||
|
||||
return (self.DRONE_ORDER.index(groupName),
|
||||
drone.item.name)
|
||||
return (DRONE_ORDER.index(groupName), drone.item.name)
|
||||
|
||||
def fitChanged(self, event):
|
||||
event.Skip()
|
||||
|
||||
@@ -34,6 +34,9 @@ from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
|
||||
class FighterViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(FighterViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -250,11 +253,10 @@ class FighterDisplay(d.Display):
|
||||
def _merge(src, dst):
|
||||
return
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
def fighterKey(self, fighter):
|
||||
@staticmethod
|
||||
def fighterKey(fighter):
|
||||
groupName = Market.getInstance().getGroupByItem(fighter.item).name
|
||||
orderPos = self.FIGHTER_ORDER.index(groupName)
|
||||
orderPos = FIGHTER_ORDER.index(groupName)
|
||||
# Sort support fighters by name, ignore their abilities
|
||||
if groupName == 'Support Fighter':
|
||||
abilityEffectIDs = ()
|
||||
|
||||
@@ -299,7 +299,10 @@ class ImplantDisplay(d.Display):
|
||||
sourceContext1 = "implantItem" if fit.implantSource == ImplantLocation.FIT else "implantItemChar"
|
||||
sourceContext2 = "implantItemMisc" if fit.implantSource == ImplantLocation.FIT else "implantItemMiscChar"
|
||||
itemContext = None if mainImplant is None else Market.getInstance().getCategoryByItem(mainImplant.item).name
|
||||
menu = ContextMenu.getMenu(self, mainImplant, selection, (sourceContext1, itemContext), (sourceContext2, itemContext))
|
||||
menu = ContextMenu.getMenu(self, mainImplant, selection,
|
||||
(sourceContext1, itemContext),
|
||||
(sourceContext2, itemContext)
|
||||
)
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
@@ -313,7 +316,7 @@ class ImplantDisplay(d.Display):
|
||||
implants.append(implant)
|
||||
return implants
|
||||
|
||||
def addImplantSet(self, impSet):
|
||||
def addImplants(self, implants):
|
||||
self.mainFrame.command.Submit(cmd.GuiAddImplantSetCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
itemIDs=[i.itemID for i in impSet.implants]))
|
||||
itemIDs=[i.itemID for i in implants]))
|
||||
|
||||
@@ -90,8 +90,6 @@ class ProjectedView(d.Display):
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.droneView = gui.builtinAdditionPanes.droneView.DroneView
|
||||
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.SetDropTarget(ProjectedViewDrop(self.handleListDrag))
|
||||
@@ -119,12 +117,12 @@ class ProjectedView(d.Display):
|
||||
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
|
||||
elif data[0] == 'market':
|
||||
itemID = int(data[1])
|
||||
category = Market.getInstance().getItem(itemID, eager=('group.category')).category.name
|
||||
if category == 'Module':
|
||||
item = Market.getInstance().getItem(itemID)
|
||||
if item.isModule:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Drone':
|
||||
elif item.isDrone:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedDroneCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Fighter':
|
||||
elif item.isFighter:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=itemID))
|
||||
|
||||
def kbEvent(self, event):
|
||||
@@ -162,7 +160,7 @@ class ProjectedView(d.Display):
|
||||
if item.marketGroup is None:
|
||||
item = item.metaGroup.parent
|
||||
|
||||
return (self.droneView.DRONE_ORDER.index(item.marketGroup.name),
|
||||
return (gui.builtinAdditionPanes.droneView.DRONE_ORDER.index(item.marketGroup.name),
|
||||
drone.item.name)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -6,6 +6,7 @@ from gui.builtinContextMenus import fitAddCurrentlyOpen
|
||||
from gui.builtinContextMenus import envEffectAdd
|
||||
from gui.builtinContextMenus import commandFitAdd
|
||||
from gui.builtinContextMenus.targetProfile import adder
|
||||
from gui.builtinContextMenus import graphFitAmmoPicker
|
||||
# Often-used item manipulations
|
||||
from gui.builtinContextMenus import shipModeChange
|
||||
from gui.builtinContextMenus import moduleAmmoChange
|
||||
@@ -36,7 +37,8 @@ from gui.builtinContextMenus import cargoAdd
|
||||
from gui.builtinContextMenus import cargoAddAmmo
|
||||
from gui.builtinContextMenus import itemProject
|
||||
from gui.builtinContextMenus import ammoToDmgPattern
|
||||
from gui.builtinContextMenus import implantSetAdd
|
||||
from gui.builtinContextMenus import implantSetApply
|
||||
from gui.builtinContextMenus import implantSetSave
|
||||
# Price
|
||||
from gui.builtinContextMenus import priceOptions
|
||||
# Resistance panel
|
||||
|
||||
@@ -3,7 +3,6 @@ from gui.contextMenu import ContextMenuUnconditional
|
||||
from gui.utils.clipboard import toClipboard
|
||||
from service.fit import Fit
|
||||
from service.port.eft import exportDrones, exportFighters, exportCargo, exportImplants, exportBoosters
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
viewSpecMap = {
|
||||
@@ -17,12 +16,12 @@ viewSpecMap = {
|
||||
|
||||
class AdditionsExportAll(ContextMenuUnconditional):
|
||||
|
||||
visibilitySetting = 'additionsCopyPaste'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
|
||||
return False
|
||||
if srcContext not in viewSpecMap:
|
||||
return False
|
||||
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
@@ -3,7 +3,6 @@ from gui.contextMenu import ContextMenuSelection
|
||||
from gui.utils.clipboard import toClipboard
|
||||
from service.fit import Fit
|
||||
from service.port.eft import exportDrones, exportFighters, exportCargo, exportImplants, exportBoosters
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
viewSpecMap = {
|
||||
@@ -17,12 +16,12 @@ viewSpecMap = {
|
||||
|
||||
class AdditionsExportAll(ContextMenuSelection):
|
||||
|
||||
visibilitySetting = 'additionsCopyPaste'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, selection):
|
||||
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
|
||||
return False
|
||||
if srcContext not in viewSpecMap:
|
||||
return False
|
||||
if not selection:
|
||||
|
||||
@@ -4,7 +4,6 @@ from gui.contextMenu import ContextMenuUnconditional
|
||||
from gui.utils.clipboard import fromClipboard
|
||||
from service.fit import Fit
|
||||
from service.port.eft import parseAdditions
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
viewSpecMap = {
|
||||
@@ -18,12 +17,12 @@ viewSpecMap = {
|
||||
|
||||
class AdditionsImport(ContextMenuUnconditional):
|
||||
|
||||
visibilitySetting = 'additionsCopyPaste'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
|
||||
return False
|
||||
if srcContext not in viewSpecMap:
|
||||
return False
|
||||
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
@@ -5,19 +5,16 @@ import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class AmmoToDmgPattern(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'ammoPattern'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if not self.settings.get('ammoPattern'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ class AddToCargo(ContextMenuSingle):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
# Make sure context menu registers in the correct view
|
||||
if not fit or fit.isStructure:
|
||||
|
||||
if not fit or (fit.isStructure and mainItem.category.ID != 8):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
@@ -6,7 +7,8 @@ import wx
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.damagePattern import DamagePattern as import_DamagePattern
|
||||
from gui.utils.sorter import smartSort
|
||||
from service.damagePattern import DamagePattern as DmgPatternSvc
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
@@ -23,97 +25,90 @@ class ChangeDamagePattern(ContextMenuUnconditional):
|
||||
return self.mainFrame.getActiveFit() is not None
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
sDP = import_DamagePattern.getInstance()
|
||||
sDP = DmgPatternSvc.getInstance()
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.fit = sFit.getFit(fitID)
|
||||
|
||||
self.patterns = sDP.getDamagePatternList()
|
||||
self.patterns.sort(key=lambda p: (p.name not in ["Uniform", "Selected Ammo"], p.name))
|
||||
builtinPatterns = sDP.getBuiltinDamagePatternList()
|
||||
userPatterns = sorted(sDP.getUserDamagePatternList(), key=lambda p: smartSort(p.fullName))
|
||||
# Order here is important: patterns with duplicate names from the latter will overwrite
|
||||
# patterns from the former
|
||||
self.patterns = sorted(
|
||||
chain(builtinPatterns, userPatterns),
|
||||
key=lambda p: p.fullName not in ["Uniform", "Selected Ammo"])
|
||||
|
||||
self.patternIds = {}
|
||||
self.subMenus = OrderedDict()
|
||||
self.singles = []
|
||||
|
||||
# iterate and separate damage patterns based on "[Parent] Child"
|
||||
self.patternEventMap = {}
|
||||
self.items = (OrderedDict(), OrderedDict())
|
||||
for pattern in self.patterns:
|
||||
start, end = pattern.name.find('['), pattern.name.find(']')
|
||||
if start is not -1 and end is not -1:
|
||||
currBase = pattern.name[start + 1:end]
|
||||
name = pattern.name[end + 1:].strip()
|
||||
if not name:
|
||||
self.singles.append(pattern)
|
||||
continue
|
||||
# set helper attr
|
||||
setattr(pattern, "_name", name)
|
||||
if currBase not in self.subMenus:
|
||||
self.subMenus[currBase] = []
|
||||
self.subMenus[currBase].append(pattern)
|
||||
else:
|
||||
self.singles.append(pattern)
|
||||
container = self.items
|
||||
for categoryName in pattern.hierarchy:
|
||||
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
|
||||
container[0][pattern.shortName] = pattern
|
||||
|
||||
# return list of names, with singles first followed by submenu names
|
||||
self.m = [p.name for p in self.singles] + list(self.subMenus.keys())
|
||||
return self.m
|
||||
return list(self.items[0].keys()) + list(self.items[1].keys())
|
||||
|
||||
def addPattern(self, rootMenu, pattern):
|
||||
def _addPattern(self, parentMenu, pattern, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
menuItem = wx.MenuItem(rootMenu, id, name, kind=wx.ITEM_CHECK)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
|
||||
|
||||
# set pattern attr to menu item
|
||||
menuItem.pattern = pattern
|
||||
self.patternEventMap[id] = pattern
|
||||
menuItem = wx.MenuItem(parentMenu, id, name, kind=wx.ITEM_CHECK)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
|
||||
|
||||
# determine active pattern
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit:
|
||||
dp = fit.damagePattern
|
||||
checked = dp is pattern
|
||||
else:
|
||||
checked = False
|
||||
checked = fit.damagePattern is pattern if fit else False
|
||||
return menuItem, checked
|
||||
|
||||
def _addCategory(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
|
||||
return menuItem
|
||||
|
||||
def isChecked(self, i):
|
||||
try:
|
||||
single = self.singles[i]
|
||||
patternName = list(self.items[0].keys())[i]
|
||||
except IndexError:
|
||||
return super().isChecked(i)
|
||||
if self.fit and single is self.fit.damagePattern:
|
||||
pattern = self.items[0][patternName]
|
||||
if self.fit and pattern is self.fit.damagePattern:
|
||||
return True
|
||||
return False
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
# Attempt to remove attribute which carries info if non-sub-items should
|
||||
# be checked or not
|
||||
self.checked = None
|
||||
|
||||
if self.m[i] not in self.subMenus:
|
||||
# if we're trying to get submenu to something that shouldn't have one,
|
||||
# redirect event of the item to handlePatternSwitch and put pattern in
|
||||
# our patternIds mapping, then return None for no submenu
|
||||
# Pattern as menu item
|
||||
if i < len(self.items[0]):
|
||||
id = pitem.GetId()
|
||||
self.patternIds[id] = self.singles[i]
|
||||
self.patternEventMap[id] = list(self.items[0].values())[i]
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
|
||||
return False
|
||||
|
||||
sub = wx.Menu()
|
||||
|
||||
# Items that have a parent
|
||||
# Category as menu item - expands further
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
for pattern in self.subMenus[self.m[i]]:
|
||||
mitem, checked = self.addPattern(rootMenu if msw else sub, pattern)
|
||||
sub.Append(mitem)
|
||||
mitem.Check(checked)
|
||||
|
||||
return sub
|
||||
def makeMenu(container, parentMenu):
|
||||
menu = wx.Menu()
|
||||
for name, subcontainer in container[1].items():
|
||||
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
|
||||
subMenu = makeMenu(subcontainer, menu)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
menu.Append(menuItem)
|
||||
for name, pattern in container[0].items():
|
||||
menuItem, checked = self._addPattern(rootMenu if msw else parentMenu, pattern, name)
|
||||
menu.Append(menuItem)
|
||||
menuItem.Check(checked)
|
||||
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch)
|
||||
return menu
|
||||
|
||||
container = list(self.items[1].values())[i - len(self.items[0])]
|
||||
subMenu = makeMenu(container, rootMenu)
|
||||
return subMenu
|
||||
|
||||
def handlePatternSwitch(self, event):
|
||||
pattern = self.patternIds.get(event.Id, False)
|
||||
pattern = self.patternEventMap.get(event.Id, False)
|
||||
if pattern is False:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -10,6 +11,28 @@ from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class Group:
|
||||
|
||||
def __init__(self):
|
||||
self.groups = OrderedDict()
|
||||
self.items = []
|
||||
|
||||
def sort(self):
|
||||
self.groups = OrderedDict((k, self.groups[k]) for k in sorted(self.groups))
|
||||
for group in self.groups.values():
|
||||
group.sort()
|
||||
self.items.sort(key=lambda e: e.shortName)
|
||||
|
||||
|
||||
class Entry:
|
||||
|
||||
def __init__(self, itemID, name, shortName):
|
||||
self.itemID = itemID
|
||||
self.name = name
|
||||
self.shortName = shortName
|
||||
|
||||
|
||||
|
||||
class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
|
||||
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
|
||||
@@ -32,107 +55,78 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return "Add Environmental Effect"
|
||||
|
||||
def _addGroup(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _addEffect(self, parentMenu, typeID, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.idmap[id] = typeID
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
|
||||
# Wormholes
|
||||
|
||||
self.idmap = {}
|
||||
sub = wx.Menu()
|
||||
data = self.getData()
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
|
||||
wormhole_item = wx.MenuItem(sub, wx.ID_ANY, "Wormhole")
|
||||
wormhole_menu = wx.Menu()
|
||||
wormhole_item.SetSubMenu(wormhole_menu)
|
||||
sub.Append(wormhole_item)
|
||||
|
||||
grouped_data, flat_data = self.getEffectBeacons()
|
||||
self.buildMenu(grouped_data, flat_data, wormhole_menu, rootMenu, msw)
|
||||
|
||||
# Incursions
|
||||
|
||||
grouped_data, flat_data = self.getEffectBeacons(incursions=True)
|
||||
self.buildMenu(grouped_data, flat_data, sub, rootMenu, msw)
|
||||
|
||||
# Abyssal Weather
|
||||
|
||||
abyssal_item = wx.MenuItem(sub, wx.ID_ANY, "Abyssal Weather")
|
||||
abyssal_menu = wx.Menu()
|
||||
abyssal_item.SetSubMenu(abyssal_menu)
|
||||
sub.Append(abyssal_item)
|
||||
|
||||
grouped_data, flat_data = self.getAbyssalWeather()
|
||||
self.buildMenu(grouped_data, flat_data, abyssal_menu, rootMenu, msw)
|
||||
|
||||
# Localized Weather
|
||||
|
||||
local_item = wx.MenuItem(sub, wx.ID_ANY, "Localized")
|
||||
local_menu = wx.Menu()
|
||||
local_item.SetSubMenu(local_menu)
|
||||
sub.Append(local_item)
|
||||
|
||||
grouped_data, flat_data = self.getLocalizedEnvironments()
|
||||
self.buildMenu(grouped_data, flat_data, local_menu, rootMenu, msw)
|
||||
def makeMenu(data, parentMenu):
|
||||
menu = wx.Menu()
|
||||
for group_name in data.groups:
|
||||
menuItem = self._addGroup(rootMenu if msw else parentMenu, group_name)
|
||||
subMenu = makeMenu(data.groups[group_name], menu)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
menu.Append(menuItem)
|
||||
for entry in data.items:
|
||||
menuItem = self._addEffect(rootMenu if msw else parentMenu, entry.itemID, entry.shortName)
|
||||
menu.Append(menuItem)
|
||||
menu.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
return menu
|
||||
|
||||
sub = makeMenu(data, rootMenu)
|
||||
return sub
|
||||
|
||||
def handleSelection(self, event):
|
||||
# Skip events ids that aren't mapped
|
||||
|
||||
swObj, swName = self.idmap.get(event.Id, (False, False))
|
||||
if not swObj and not swName:
|
||||
swObj = self.idmap.get(event.Id, False)
|
||||
if not swObj:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID, swObj.ID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID, swObj))
|
||||
|
||||
def buildMenu(self, grouped_data, flat_data, local_menu, rootMenu, msw):
|
||||
def getData(self):
|
||||
data = Group()
|
||||
data.groups['Metaliminal Storm'] = self.getEffectBeacons(
|
||||
'Electrical', 'Exotic', 'Gamma', 'Plasma',
|
||||
extra_garbage=('Metaliminal', 'Storm', 'Matter', 'Ray', 'Firestorm'))
|
||||
data.groups['Wormhole'] = self.getEffectBeacons(
|
||||
'Black Hole', 'Cataclysmic Variable', 'Magnetar',
|
||||
'Pulsar', 'Red Giant', 'Wolf Rayet')
|
||||
data.groups['Abyssal Weather'] = self.getAbyssalWeather()
|
||||
data.groups['Sansha Incursion'] = self.getEffectBeacons('Sansha Incursion')
|
||||
data.groups['Triglavian Invasion'] = self.getEffectBeacons('Triglavian Invasion')
|
||||
data.groups['Triglavian Invasion'].groups['Destructible Beacons'] = self.getDestructibleBeacons()
|
||||
return data
|
||||
|
||||
def processFlat(data, root, sub):
|
||||
for swData in sorted(data, key=lambda tpl: tpl[2]):
|
||||
wxid = ContextMenuUnconditional.nextID()
|
||||
swObj, swName, swClass = swData
|
||||
self.idmap[wxid] = (swObj, swName)
|
||||
subItem = wx.MenuItem(sub, wxid, swClass)
|
||||
if msw:
|
||||
root.Bind(wx.EVT_MENU, self.handleSelection, subItem)
|
||||
else:
|
||||
sub.Bind(wx.EVT_MENU, self.handleSelection, subItem)
|
||||
sub.Append(subItem)
|
||||
|
||||
for swType in sorted(grouped_data):
|
||||
subItem = wx.MenuItem(local_menu, wx.ID_ANY, swType)
|
||||
grandSub = wx.Menu()
|
||||
subItem.SetSubMenu(grandSub)
|
||||
local_menu.Append(subItem)
|
||||
processFlat(grouped_data[swType], rootMenu, grandSub)
|
||||
|
||||
processFlat(flat_data, rootMenu, local_menu)
|
||||
|
||||
def getEffectBeacons(self, incursions=False):
|
||||
def getEffectBeacons(self, *groups, extra_garbage=()):
|
||||
"""
|
||||
Get dictionary with wormhole system-wide effects
|
||||
Get dictionary with system-wide effects
|
||||
"""
|
||||
compacted = len(groups) <= 1
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
# todo: rework this
|
||||
# Container for system-wide effects
|
||||
grouped = {}
|
||||
|
||||
# Expressions for matching when detecting effects we're looking for
|
||||
if incursions:
|
||||
validgroups = ("Incursion ship attributes effects",
|
||||
"Invasion Effects")
|
||||
else:
|
||||
validgroups = ("Black Hole Effect Beacon",
|
||||
"Cataclysmic Variable Effect Beacon",
|
||||
"Magnetar Effect Beacon",
|
||||
"Pulsar Effect Beacon",
|
||||
"Red Giant Beacon",
|
||||
"Wolf Rayet Effect Beacon")
|
||||
data = Group()
|
||||
|
||||
# Stuff we don't want to see in names
|
||||
garbages = ("Effects?", "Beacon", "ship attributes effects")
|
||||
garbages = ["System Effects", "Effects"]
|
||||
garbages.extend(extra_garbage)
|
||||
|
||||
# Get group with all the system-wide beacons
|
||||
grp = sMkt.getGroup("Effect Beacon")
|
||||
@@ -140,9 +134,9 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
# Cycle through them
|
||||
for beacon in sMkt.getItemsByGroup(grp):
|
||||
# Check if it belongs to any valid group
|
||||
for group in validgroups:
|
||||
for group in groups:
|
||||
# Check beginning of the name only
|
||||
if re.match(group, beacon.name):
|
||||
if re.search(group, beacon.name):
|
||||
# Get full beacon name
|
||||
beaconname = beacon.name
|
||||
for garbage in garbages:
|
||||
@@ -159,64 +153,65 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
groupname = re.sub(garbage, "", groupname)
|
||||
groupname = re.sub(" {2,}", " ", groupname).strip()
|
||||
# Add stuff to dictionary
|
||||
if groupname not in grouped:
|
||||
grouped[groupname] = set()
|
||||
grouped[groupname].add((beacon, beaconname, shortname))
|
||||
if compacted:
|
||||
container = data.items
|
||||
else:
|
||||
container = data.groups.setdefault(groupname, Group()).items
|
||||
container.append(Entry(beacon.ID, beaconname, shortname))
|
||||
# Break loop on 1st result
|
||||
break
|
||||
|
||||
return grouped, ()
|
||||
data.sort()
|
||||
return data
|
||||
|
||||
def getAbyssalWeather(self):
|
||||
sMkt = Market.getInstance()
|
||||
data = Group()
|
||||
|
||||
environments = {x.ID: x for x in sMkt.getGroup("Abyssal Environment").items}
|
||||
items = chain(sMkt.getGroup("MassiveEnvironments").items, sMkt.getGroup("Non-Interactable Object").items)
|
||||
|
||||
grouped = {}
|
||||
flat = set()
|
||||
|
||||
items = chain(
|
||||
sMkt.getGroup("MassiveEnvironments").items,
|
||||
sMkt.getGroup("Non-Interactable Object").items)
|
||||
for beacon in items:
|
||||
if not beacon.isType('projected'):
|
||||
continue
|
||||
|
||||
type = self.__class__.abyssal_mapping.get(beacon.name[0:-2], None)
|
||||
type = environments.get(type, None)
|
||||
if type is None:
|
||||
continue
|
||||
|
||||
if type.name not in grouped:
|
||||
grouped[type.name] = set()
|
||||
|
||||
subdata = data.groups.setdefault(type.name, Group())
|
||||
display_name = "{} {}".format(type.name, beacon.name[-1:])
|
||||
grouped[type.name].add((beacon, display_name, display_name))
|
||||
subdata.items.append(Entry(beacon.ID, display_name, display_name))
|
||||
data.sort()
|
||||
|
||||
# Localized abyssal hazards
|
||||
items = sMkt.getGroup("Abyssal Hazards").items
|
||||
if items:
|
||||
subdata = data.groups.setdefault('Localized', Group())
|
||||
for beacon in sMkt.getGroup("Abyssal Hazards").items:
|
||||
if not beacon.isType('projected'):
|
||||
continue
|
||||
# Localized effects, currently, have a name like "(size) (type) Cloud"
|
||||
# Until this inevitably changes, do a simple split
|
||||
name_parts = beacon.name.split(" ")
|
||||
|
||||
key = name_parts[1].strip()
|
||||
subsubdata = subdata.groups.setdefault(key, Group())
|
||||
subsubdata.items.append(Entry(beacon.ID, beacon.name, beacon.name))
|
||||
subdata.sort()
|
||||
|
||||
# PVP weather
|
||||
flat.add((sMkt.getItem(49766), 'PvP Weather', 'PvP Weather'))
|
||||
data.items.append(Entry(49766, 'PvP Weather', 'PvP Weather'))
|
||||
|
||||
return grouped, flat
|
||||
return data
|
||||
|
||||
def getLocalizedEnvironments(self):
|
||||
def getDestructibleBeacons(self):
|
||||
data = Group()
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
grp = sMkt.getGroup("Abyssal Hazards")
|
||||
|
||||
grouped = dict()
|
||||
|
||||
for beacon in grp.items:
|
||||
if not beacon.isType('projected'):
|
||||
for item in sMkt.getItemsByGroup(sMkt.getGroup('Destructible Effect Beacon')):
|
||||
if not item.isType('projected'):
|
||||
continue
|
||||
# Localized effects, currently, have a name like "(size) (type) Cloud"
|
||||
# Until this inevitably changes, do a simple split
|
||||
name_parts = beacon.name.split(" ")
|
||||
|
||||
key = name_parts[1].strip()
|
||||
if key not in grouped:
|
||||
grouped[key] = set()
|
||||
|
||||
grouped[key].add((beacon, beacon.name, beacon.name))
|
||||
|
||||
return grouped, ()
|
||||
|
||||
data.items.append(Entry(item.ID, item.name, item.name))
|
||||
data.sort()
|
||||
return data
|
||||
|
||||
AddEnvironmentEffect.register()
|
||||
|
||||
241
gui/builtinContextMenus/graphFitAmmoPicker.py
Normal file
241
gui/builtinContextMenus/graphFitAmmoPicker.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.auxWindow import AuxiliaryDialog
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.ammo import Ammo
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class GraphFitAmmoPicker(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if srcContext != 'graphFitList':
|
||||
return False
|
||||
if mainItem is None or not mainItem.isFit:
|
||||
return False
|
||||
if callingWindow.graphFrame.getView().internalName != 'dmgStatsGraph':
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem):
|
||||
return 'Plot with Different Ammo...'
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, i):
|
||||
AmmoPickerFrame.openOne(callingWindow, mainItem.item, forceReopen=True)
|
||||
|
||||
|
||||
# GraphFitAmmoPicker.register()
|
||||
|
||||
|
||||
class AmmoPickerFrame(AuxiliaryDialog):
|
||||
|
||||
def __init__(self, parent, fit):
|
||||
super().__init__(parent, title='Choose Different Ammo', style=wx.DEFAULT_DIALOG_STYLE, resizeable=True)
|
||||
padding = 5
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
contents = AmmoPickerContents(self, fit)
|
||||
mainSizer.Add(contents, 1, wx.EXPAND | wx.ALL, padding)
|
||||
|
||||
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
||||
if buttonSizer:
|
||||
mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, padding)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
|
||||
contW, contH = contents.GetVirtualSize()
|
||||
bestW = contW + padding * 2
|
||||
bestH = contH + padding * 2
|
||||
if buttonSizer:
|
||||
# Yeah right... whatever
|
||||
buttW, buttH = buttonSizer.GetSize()
|
||||
bestW = max(bestW, buttW + padding * 2)
|
||||
bestH += buttH + padding * 2
|
||||
bestW = min(1000, bestW)
|
||||
bestH = min(700, bestH)
|
||||
self.SetSize(bestW, bestH)
|
||||
self.SetMinSize(wx.Size(int(bestW * 0.7), int(bestH * 0.7)))
|
||||
self.CenterOnParent()
|
||||
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
|
||||
|
||||
def kbEvent(self, event):
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
|
||||
class AmmoPickerContents(wx.ScrolledCanvas):
|
||||
|
||||
indent = 15
|
||||
|
||||
def __init__(self, parent, fit):
|
||||
wx.ScrolledCanvas.__init__(self, parent)
|
||||
self.SetScrollRate(0, 15)
|
||||
|
||||
mods = self.getMods(fit)
|
||||
drones = self.getDrones(fit)
|
||||
fighters = self.getFighters(fit)
|
||||
self.rbLabelMap = {}
|
||||
self.rbCheckboxMap = {}
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
moduleSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(moduleSizer, 0, wx.ALL, 0)
|
||||
|
||||
self.droneSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(self.droneSizer, 0, wx.ALL, 0)
|
||||
|
||||
fighterSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
mainSizer.Add(fighterSizer, 0, wx.ALL, 0)
|
||||
|
||||
firstRadio = True
|
||||
|
||||
for modInfo, modAmmo in mods:
|
||||
text = '\n'.join('{}x {}'.format(amount, item.name) for item, amount in modInfo)
|
||||
modRb = self.addRadioButton(moduleSizer, text, firstRadio)
|
||||
firstRadio = False
|
||||
# Get actual module, as ammo getters need it
|
||||
mod = next((m for m in fit.modules if m.itemID == next(iter(modInfo))[0].ID), None)
|
||||
_, ammoTree = Ammo.getInstance().getModuleStructuredAmmo(mod)
|
||||
if len(ammoTree) == 1:
|
||||
for ammoCatName, ammos in ammoTree.items():
|
||||
for ammo in ammos:
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
||||
else:
|
||||
for ammoCatName, ammos in ammoTree.items():
|
||||
if len(ammos) == 1:
|
||||
ammo = next(iter(ammos))
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
||||
else:
|
||||
self.addLabel(moduleSizer, '{}:'.format(ammoCatName), modRb, indentLvl=1)
|
||||
for ammo in ammos:
|
||||
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=2)
|
||||
if drones:
|
||||
droneRb = self.addRadioButton(self.droneSizer, 'Drones', firstRadio)
|
||||
from gui.builtinAdditionPanes.droneView import DroneView
|
||||
for drone in sorted(drones, key=DroneView.droneKey):
|
||||
self.addCheckbox(self.droneSizer, '{}x {}'.format(drone.amount, drone.item.name), droneRb, indentLvl=1)
|
||||
addBtn = wx.Button(self, wx.ID_ANY, '+', style=wx.BU_EXACTFIT)
|
||||
addBtn.Bind(wx.EVT_BUTTON, self.OnDroneGroupAdd)
|
||||
mainSizer.Add(addBtn, 0, wx.LEFT, self.indent)
|
||||
if fighters:
|
||||
fighterRb = self.addRadioButton(fighterSizer, 'Fighters', firstRadio)
|
||||
from gui.builtinAdditionPanes.fighterView import FighterDisplay
|
||||
for fighter in sorted(fighters, key=FighterDisplay.fighterKey):
|
||||
self.addCheckbox(fighterSizer, '{}x {}'.format(fighter.amount, fighter.item.name), fighterRb, indentLvl=1)
|
||||
|
||||
self.SetSizer(mainSizer)
|
||||
self.refreshStatus()
|
||||
|
||||
def addRadioButton(self, sizer, text, firstRadio=False):
|
||||
if firstRadio:
|
||||
rb = wx.RadioButton(self, wx.ID_ANY, text, style=wx.RB_GROUP)
|
||||
rb.SetValue(True)
|
||||
else:
|
||||
rb = wx.RadioButton(self, wx.ID_ANY, text)
|
||||
rb.SetValue(False)
|
||||
rb.Bind(wx.EVT_RADIOBUTTON, self.rbSelected)
|
||||
sizer.Add(rb, 0, wx.EXPAND | wx.ALL, 0)
|
||||
return rb
|
||||
|
||||
def addCheckbox(self, sizer, text, currentRb, indentLvl=0):
|
||||
cb = wx.CheckBox(self, -1, text)
|
||||
sizer.Add(cb, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
||||
if currentRb is not None:
|
||||
self.rbCheckboxMap.setdefault(currentRb, []).append(cb)
|
||||
|
||||
def addLabel(self, sizer, text, currentRb, indentLvl=0):
|
||||
text = text[0].capitalize() + text[1:]
|
||||
label = wx.StaticText(self, wx.ID_ANY, text)
|
||||
sizer.Add(label, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
||||
if currentRb is not None:
|
||||
self.rbLabelMap.setdefault(currentRb, []).append(label)
|
||||
|
||||
def getMods(self, fit):
|
||||
sMkt = Market.getInstance()
|
||||
sAmmo = Ammo.getInstance()
|
||||
loadableChargesCache = {}
|
||||
# Modules, format: {frozenset(ammo): {item: count}}
|
||||
modsPrelim = {}
|
||||
if fit is not None:
|
||||
for mod in fit.modules:
|
||||
if not mod.canDealDamage():
|
||||
continue
|
||||
typeID = mod.item.ID
|
||||
if typeID not in loadableChargesCache:
|
||||
loadableChargesCache[typeID] = sAmmo.getModuleFlatAmmo(mod)
|
||||
charges = loadableChargesCache[typeID]
|
||||
# We're not interested in modules which contain no charges
|
||||
if charges:
|
||||
data = modsPrelim.setdefault(frozenset(charges), {})
|
||||
if mod.item not in data:
|
||||
data[mod.item] = 0
|
||||
data[mod.item] += 1
|
||||
# Format: [([(item, count), ...], frozenset(ammo)), ...]
|
||||
modsFinal = []
|
||||
for charges, itemCounts in modsPrelim.items():
|
||||
modsFinal.append((
|
||||
# Sort items within group
|
||||
sorted(itemCounts.items(), key=lambda i: sMkt.itemSort(i[0], reverseMktGrp=True), reverse=True),
|
||||
charges))
|
||||
# Sort item groups
|
||||
modsFinal.sort(key=lambda i: sMkt.itemSort(i[0][0][0], reverseMktGrp=True), reverse=True)
|
||||
return modsFinal
|
||||
|
||||
def getDrones(self, fit):
|
||||
drones = []
|
||||
if fit is not None:
|
||||
for drone in fit.drones:
|
||||
if drone.item is None:
|
||||
continue
|
||||
# Drones are our "ammo", so we want to pick even those which are inactive
|
||||
if drone.canDealDamage(ignoreState=True):
|
||||
drones.append(drone)
|
||||
continue
|
||||
if {'remoteWebifierEntity', 'remoteTargetPaintEntity'}.intersection(drone.item.effects):
|
||||
drones.append(drone)
|
||||
continue
|
||||
return drones
|
||||
|
||||
def getFighters(self, fit):
|
||||
fighters = []
|
||||
if fit is not None:
|
||||
for fighter in fit.fighters:
|
||||
if fighter.item is None:
|
||||
continue
|
||||
# Fighters are our "ammo" as well
|
||||
if fighter.canDealDamage(ignoreState=True):
|
||||
fighters.append(fighter)
|
||||
continue
|
||||
for ability in fighter.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||
fighters.append(fighter)
|
||||
break
|
||||
return fighters
|
||||
|
||||
def OnDroneGroupAdd(self, event):
|
||||
event.Skip()
|
||||
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
label = wx.StaticText()
|
||||
self.droneSizer.Add(sizer, 0, wx.EXPAND | wx.LEFT, self.indent)
|
||||
|
||||
def refreshStatus(self):
|
||||
for map in (self.rbLabelMap, self.rbCheckboxMap):
|
||||
for rb, items in map.items():
|
||||
for item in items:
|
||||
item.Enable(rb.GetValue())
|
||||
|
||||
def rbSelected(self, event):
|
||||
event.Skip()
|
||||
self.refreshStatus()
|
||||
@@ -1,52 +0,0 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.implantSet import ImplantSets as s_ImplantSets
|
||||
|
||||
|
||||
class AddImplantSet(ContextMenuUnconditional):
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
|
||||
sIS = s_ImplantSets.getInstance()
|
||||
implantSets = sIS.getImplantSetList()
|
||||
|
||||
if len(implantSets) == 0:
|
||||
return False
|
||||
return srcContext in ("implantItemMisc", "implantEditor")
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return "Add Implant Set"
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
bindmenu = rootMenu if "wxMSW" in wx.PlatformInfo else m
|
||||
|
||||
sIS = s_ImplantSets.getInstance()
|
||||
implantSets = sIS.getImplantSetList()
|
||||
|
||||
self.context = context
|
||||
self.callingWindow = callingWindow
|
||||
|
||||
self.idmap = {}
|
||||
|
||||
for set in sorted(implantSets, key=lambda i: i.name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, set.name)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||
self.idmap[id] = set
|
||||
m.Append(mitem)
|
||||
|
||||
return m
|
||||
|
||||
def handleSelection(self, event):
|
||||
impSet = self.idmap.get(event.Id, None)
|
||||
if impSet is None:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
self.callingWindow.addImplantSet(impSet)
|
||||
|
||||
|
||||
AddImplantSet.register()
|
||||
108
gui/builtinContextMenus/implantSetApply.py
Normal file
108
gui/builtinContextMenus/implantSetApply.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.market import Market
|
||||
from service.implantSet import ImplantSets as UserImplantSets
|
||||
from service.precalcImplantSet import PrecalcedImplantSets
|
||||
|
||||
|
||||
class ImplantSetApply(ContextMenuUnconditional):
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
|
||||
self.userImplantSets = UserImplantSets.getInstance().getImplantSetList()
|
||||
self.structedImplantSets = PrecalcedImplantSets.getStructuredSets()
|
||||
|
||||
if len(self.userImplantSets) == 0 and len(self.structedImplantSets) == 0:
|
||||
return False
|
||||
|
||||
return srcContext in ("implantItemMisc", "implantEditor")
|
||||
|
||||
def getText(self, callingWindow, context):
|
||||
return "Apply Implant Set"
|
||||
|
||||
def _addSeparator(self, m, text):
|
||||
id_ = ContextMenuUnconditional.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
def _addSet(self, parentMenu, profile, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.eventSetMap[id] = profile
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _addCategory(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _gradeSorter(self, item):
|
||||
order = ['low-grade', 'mid-grade', 'high-grade']
|
||||
try:
|
||||
pos = order.index(item.lower())
|
||||
except IndexError:
|
||||
pos = len(order)
|
||||
return pos, item
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
menu_lvl1 = wx.Menu()
|
||||
|
||||
self.context = context
|
||||
self.callingWindow = callingWindow
|
||||
|
||||
self.eventSetMap = {}
|
||||
|
||||
# Auto-generated sets
|
||||
for setName in sorted(self.structedImplantSets):
|
||||
setData = self.structedImplantSets[setName]
|
||||
if len(setData) == 1:
|
||||
for implantIDs in setData.values():
|
||||
menuitem_lvl1 = self._addSet(rootMenu, implantIDs, setName)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
else:
|
||||
menuitem_lvl1 = self._addCategory(rootMenu, setName)
|
||||
menu_lvl2 = wx.Menu()
|
||||
for gradeName in sorted(setData, key=self._gradeSorter):
|
||||
implantIDs = setData[gradeName]
|
||||
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantIDs, gradeName)
|
||||
menu_lvl2.Append(menuitem_lvl2)
|
||||
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
menuitem_lvl1.SetSubMenu(menu_lvl2)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
|
||||
# Separator
|
||||
if self.userImplantSets and self.structedImplantSets:
|
||||
menu_lvl1.AppendSeparator()
|
||||
|
||||
# Saved sets
|
||||
if self.userImplantSets:
|
||||
menuitem_lvl1 = self._addCategory(rootMenu, 'Saved Sets')
|
||||
menu_lvl2 = wx.Menu()
|
||||
for implantSet in sorted(self.userImplantSets, key=lambda i: i.name):
|
||||
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantSet, implantSet.name)
|
||||
menu_lvl2.Append(menuitem_lvl2)
|
||||
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
menuitem_lvl1.SetSubMenu(menu_lvl2)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
|
||||
menu_lvl1.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
return menu_lvl1
|
||||
|
||||
def handleSelection(self, event):
|
||||
impSet = self.eventSetMap.get(event.Id, None)
|
||||
if impSet is None:
|
||||
event.Skip()
|
||||
return
|
||||
if isinstance(impSet, str):
|
||||
implants = PrecalcedImplantSets.stringToImplants(impSet)
|
||||
else:
|
||||
implants = impSet.implants
|
||||
self.callingWindow.addImplants(implants)
|
||||
|
||||
|
||||
ImplantSetApply.register()
|
||||
77
gui/builtinContextMenus/implantSetSave.py
Normal file
77
gui/builtinContextMenus/implantSetSave.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class ImplantSetSave(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
if srcContext not in ('implantItemMisc', 'implantItemMiscChar'):
|
||||
return False
|
||||
|
||||
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())
|
||||
self.implants = fit.appliedImplants[:]
|
||||
if not self.implants:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, context):
|
||||
return 'Save as New Implant Set'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
with NameDialog(self.mainFrame, '') as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
name = dlg.input.GetLineText(0).strip()
|
||||
if name == '':
|
||||
return
|
||||
from gui.setEditor import ImplantSetEditor
|
||||
ImplantSetEditor.openOne(parent=self.mainFrame, dataToAdd=(name, self.implants))
|
||||
|
||||
|
||||
ImplantSetSave.register()
|
||||
|
||||
|
||||
class NameDialog(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, value):
|
||||
super().__init__(parent, title='New Implant Set', style=wx.DEFAULT_DIALOG_STYLE)
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, 'Enter a name for your new Implant Set:')
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
if value is None:
|
||||
value = ''
|
||||
else:
|
||||
value = str(value)
|
||||
self.input.SetValue(value)
|
||||
self.input.SelectAll()
|
||||
|
||||
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
|
||||
|
||||
bSizer3 = wx.BoxSizer(wx.VERTICAL)
|
||||
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
|
||||
|
||||
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
|
||||
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
|
||||
|
||||
self.input.SetFocus()
|
||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
|
||||
self.SetSizer(bSizer1)
|
||||
self.CenterOnParent()
|
||||
self.Fit()
|
||||
|
||||
def processEnter(self, evt):
|
||||
self.EndModal(wx.ID_OK)
|
||||
@@ -1,19 +1,16 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FillWithItem(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'moduleFill'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if not self.settings.get('moduleFill'):
|
||||
return False
|
||||
|
||||
if srcContext not in ('marketItemGroup', 'marketItemMisc'):
|
||||
return False
|
||||
|
||||
|
||||
@@ -2,19 +2,16 @@ import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ProjectItem(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'project'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if not self.settings.get('project'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -7,19 +7,16 @@ from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions, getSimilarFighters
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
visibilitySetting = 'metaSwap'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if not self.settings.get('metaSwap'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in (
|
||||
'fittingModule',
|
||||
'droneItem',
|
||||
@@ -59,8 +56,11 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
return x.metaLevel or 0
|
||||
|
||||
def get_metagroup(x):
|
||||
# We want deadspace before officer mods
|
||||
remap = {5: 6, 6: 5}
|
||||
remap = {
|
||||
# We want deadspace before officer mods
|
||||
5: 6, 6: 5,
|
||||
# For structures we want t1-t2-faction
|
||||
54: 52, 52: 54}
|
||||
metaGroup = sMkt.getMetaGroupByItem(x)
|
||||
return remap.get(metaGroup.ID, metaGroup.ID) if metaGroup is not None else 0
|
||||
|
||||
|
||||
@@ -3,33 +3,28 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions
|
||||
from service.ammo import Ammo
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class ChangeModuleAmmo(ContextMenuCombined):
|
||||
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
# Format: {type ID: set(loadable, charges)}
|
||||
self.loadableCharges = {}
|
||||
self.loadableChargesCache = {}
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if srcContext not in ("fittingModule", "projectedModule"):
|
||||
if srcContext not in ('fittingModule', 'projectedModule'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
self.mainCharges = self.getChargesForMod(mainItem)
|
||||
self.mainCharges = self._getAmmo(mainItem)
|
||||
if not self.mainCharges:
|
||||
return False
|
||||
|
||||
@@ -39,186 +34,81 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return "Charge"
|
||||
return 'Charge'
|
||||
|
||||
def getChargesForMod(self, mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
def _getAmmo(self, mod):
|
||||
if mod.itemID is None:
|
||||
return set()
|
||||
typeID = mod.item.ID
|
||||
if typeID in self.loadableCharges:
|
||||
return self.loadableCharges[typeID]
|
||||
chargeSet = self.loadableCharges.setdefault(typeID, set())
|
||||
# Do not try to grab it for modes which can also be passed as part of selection
|
||||
if isinstance(mod, Module):
|
||||
for charge in mod.getValidCharges():
|
||||
if sMkt.getPublicityByItem(charge):
|
||||
chargeSet.add(charge)
|
||||
return chargeSet
|
||||
if mod.itemID not in self.loadableChargesCache:
|
||||
self.loadableChargesCache[mod.itemID] = Ammo.getInstance().getModuleFlatAmmo(mod)
|
||||
return self.loadableChargesCache[mod.itemID]
|
||||
|
||||
def turretSorter(self, charge):
|
||||
damage = 0
|
||||
range_ = (self.module.item.getAttribute("maxRange")) * \
|
||||
(charge.getAttribute("weaponRangeMultiplier") or 1)
|
||||
falloff = (self.module.item.getAttribute("falloff") or 0) * \
|
||||
(charge.getAttribute("fallofMultiplier") or 1)
|
||||
for type_ in self.DAMAGE_TYPES:
|
||||
d = charge.getAttribute("%sDamage" % type_)
|
||||
if d > 0:
|
||||
damage += d
|
||||
|
||||
# Take optimal and falloff as range factor
|
||||
rangeFactor = range_ + falloff
|
||||
|
||||
return - rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
def missileSorter(self, charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = self.damageInfo(charge)
|
||||
# Find its position in sort list
|
||||
position = self.MISSILE_ORDER.index(chargeDamageType)
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
def damageInfo(self, charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
damageMap = {}
|
||||
totalDamage = 0
|
||||
# Fill them with the data about charge
|
||||
for damageType in self.DAMAGE_TYPES:
|
||||
currentDamage = charge.getAttribute("{0}Damage".format(damageType)) or 0
|
||||
damageMap[damageType] = currentDamage
|
||||
totalDamage += currentDamage
|
||||
# Detect type of ammo
|
||||
chargeDamageType = None
|
||||
for damageType in damageMap:
|
||||
# If all damage belongs to certain type purely, set appropriate
|
||||
# ammoType
|
||||
if damageMap[damageType] == totalDamage:
|
||||
chargeDamageType = damageType
|
||||
break
|
||||
# Else consider ammo as mixed damage
|
||||
if chargeDamageType is None:
|
||||
chargeDamageType = "mixed"
|
||||
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
@staticmethod
|
||||
def numericConverter(string):
|
||||
return int(string) if string.isdigit() else string
|
||||
|
||||
def nameSorter(self, charge):
|
||||
parts = charge.name.split(" ")
|
||||
return list(map(self.numericConverter, parts))
|
||||
|
||||
def addCharge(self, menu, charge):
|
||||
def _addCharge(self, menu, charge):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
name = charge.name if charge is not None else "Empty"
|
||||
self.chargeIds[id_] = charge
|
||||
name = charge.name if charge is not None else 'Empty'
|
||||
self.chargeEventMap[id_] = charge
|
||||
item = wx.MenuItem(menu, id_, name)
|
||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
||||
item.charge = charge
|
||||
if charge is not None and charge.iconID is not None:
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, 'icons')
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def addSeperator(m, text):
|
||||
def _addSeparator(m, text):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
m = wx.Menu()
|
||||
self.chargeIds = {}
|
||||
hardpoint = self.module.hardpoint
|
||||
moduleName = self.module.item.name
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
||||
self.addSeperator(m, "Long Range")
|
||||
items = []
|
||||
range_ = None
|
||||
nameBase = None
|
||||
sub = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.turretSorter)
|
||||
for charge in chargesSorted:
|
||||
if "civilian" in charge.name.lower():
|
||||
continue
|
||||
currBase = charge.name.rsplit()[-2:]
|
||||
currRange = charge.getAttribute("weaponRangeMultiplier")
|
||||
if nameBase is None or range_ != currRange or nameBase != currBase:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
sub = None
|
||||
base = charge
|
||||
nameBase = currBase
|
||||
range_ = currRange
|
||||
item = self.addCharge(rootMenu if msw else m, charge)
|
||||
items.append(item)
|
||||
msw = True if 'wxMSW' in wx.PlatformInfo else False
|
||||
menu = wx.Menu()
|
||||
self.chargeEventMap = {}
|
||||
modType, chargeDict = Ammo.getInstance().getModuleStructuredAmmo(self.module, ammo=self.mainCharges)
|
||||
if modType == 'ddTurret':
|
||||
self._addSeparator(menu, 'Long Range')
|
||||
menuItems = []
|
||||
for charges in chargeDict.values():
|
||||
if len(charges) == 1:
|
||||
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
|
||||
else:
|
||||
if sub is None and item and base:
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, base))
|
||||
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
for item in items:
|
||||
m.Append(item)
|
||||
|
||||
self.addSeperator(m, "Short Range")
|
||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
type_ = None
|
||||
sub = None
|
||||
defender = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
|
||||
for charge in chargesSorted:
|
||||
currType = self.damageInfo(charge)[0]
|
||||
|
||||
if currType != type_ or type_ is None:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
type_ = currType
|
||||
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
|
||||
bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
m.Append(item)
|
||||
|
||||
if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"):
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
else:
|
||||
defender = charge
|
||||
|
||||
if defender is not None:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, defender))
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
else:
|
||||
chargesSorted = sorted(self.mainCharges, key=self.nameSorter)
|
||||
for charge in chargesSorted:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, charge))
|
||||
|
||||
m.Append(self.addCharge(rootMenu if msw else m, None))
|
||||
return m
|
||||
baseCharge = charges[0]
|
||||
menuItem = self._addCharge(rootMenu if msw else menu, baseCharge)
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self._addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||
self._addSeparator(subMenu, 'More Damage')
|
||||
for menuItem in menuItems:
|
||||
menu.Append(menuItem)
|
||||
self._addSeparator(menu, 'Short Range')
|
||||
elif modType == 'ddMissile':
|
||||
menuItems = []
|
||||
for chargeCatName, charges in chargeDict.items():
|
||||
menuItem = wx.MenuItem(menu, wx.ID_ANY, chargeCatName.capitalize())
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self._addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
|
||||
self._addSeparator(subMenu, 'More Damage')
|
||||
for menuItem in menuItems:
|
||||
menu.Append(menuItem)
|
||||
elif modType == 'general':
|
||||
for charge in chargeDict['general']:
|
||||
menu.Append(self._addCharge(rootMenu if msw else menu, charge))
|
||||
menu.Append(self._addCharge(rootMenu if msw else menu, None))
|
||||
return menu
|
||||
|
||||
def handleAmmoSwitch(self, event):
|
||||
charge = self.chargeIds.get(event.Id, False)
|
||||
charge = self.chargeEventMap.get(event.Id, False)
|
||||
if charge is False:
|
||||
event.Skip()
|
||||
return
|
||||
@@ -254,7 +144,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
positions = []
|
||||
for position, mod in enumerate(modContainer):
|
||||
if mod in self.selection:
|
||||
modCharges = self.getChargesForMod(mod)
|
||||
modCharges = self._getAmmo(mod)
|
||||
if modCharges.issubset(self.mainCharges):
|
||||
positions.append(position)
|
||||
self.mainFrame.command.Submit(command(
|
||||
|
||||
@@ -2,20 +2,16 @@ import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FillWithModule(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'moduleFill'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
|
||||
if not self.settings.get('moduleFill'):
|
||||
return False
|
||||
|
||||
if mainItem is None or getattr(mainItem, 'isEmpty', False):
|
||||
return False
|
||||
|
||||
|
||||
@@ -9,20 +9,17 @@ import gui.mainFrame
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeModuleSpool(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'spoolup'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.resetId = None
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if not self.settings.get('spoolup'):
|
||||
return False
|
||||
|
||||
if srcContext not in ('fittingModule', 'projectedModule') or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -8,19 +8,16 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.character import Character
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeAffectingSkills(ContextMenuSingle):
|
||||
|
||||
visibilitySetting = 'changeAffectingSkills'
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem):
|
||||
if not self.settings.get('changeAffectingSkills'):
|
||||
return False
|
||||
|
||||
if srcContext not in (
|
||||
"fittingModule", "fittingCharge",
|
||||
"fittingShip", "droneItem",
|
||||
|
||||
@@ -7,6 +7,7 @@ import wx
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from gui.utils.sorter import smartSort
|
||||
from service.targetProfile import TargetProfile as svc_TargetProfile
|
||||
|
||||
|
||||
@@ -18,74 +19,68 @@ class TargetProfileAdder(ContextMenuUnconditional):
|
||||
def display(self, callingWindow, srcContext):
|
||||
if srcContext != 'graphTgtList':
|
||||
return False
|
||||
|
||||
sTR = svc_TargetProfile.getInstance()
|
||||
self.callingWindow = callingWindow
|
||||
self.profiles = sTR.getTargetProfileList()
|
||||
self.profiles.sort(key=lambda p: (p.name in ['None'], p.name))
|
||||
|
||||
return len(self.profiles) > 0
|
||||
# We always show "Ideal Profile" anyway
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Add Target Profile'
|
||||
|
||||
def handleProfileAdd(self, event):
|
||||
profile = self.profileIds.get(event.Id, False)
|
||||
profile = self.eventProfileMap.get(event.Id, False)
|
||||
if profile is False:
|
||||
event.Skip()
|
||||
return
|
||||
self.callingWindow.addProfile(profile)
|
||||
|
||||
def addProfile(self, rootMenu, profile):
|
||||
def _addProfile(self, parentMenu, profile, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
name = getattr(profile, '_name', profile.name)
|
||||
self.eventProfileMap[id] = profile
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
|
||||
return menuItem
|
||||
|
||||
self.profileIds[id] = profile
|
||||
item = wx.MenuItem(rootMenu, id, name)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, item)
|
||||
|
||||
return item
|
||||
def _addCategory(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
self.profileIds = {}
|
||||
self.subMenus = OrderedDict()
|
||||
self.singles = []
|
||||
self.callingWindow = callingWindow
|
||||
sTR = svc_TargetProfile.getInstance()
|
||||
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
|
||||
profiles.sort(key=lambda p: smartSort(p.fullName))
|
||||
|
||||
sub = wx.Menu()
|
||||
for profile in chain([TargetProfile.getIdeal()], self.profiles):
|
||||
start, end = profile.name.find('['), profile.name.find(']')
|
||||
if start is not -1 and end is not -1:
|
||||
currBase = profile.name[start + 1:end]
|
||||
# set helper attr
|
||||
setattr(profile, '_name', profile.name[end + 1:].strip())
|
||||
if currBase not in self.subMenus:
|
||||
self.subMenus[currBase] = []
|
||||
self.subMenus[currBase].append(profile)
|
||||
else:
|
||||
self.singles.append(profile)
|
||||
self.eventProfileMap = {}
|
||||
items = (OrderedDict(), OrderedDict())
|
||||
for profile in profiles:
|
||||
container = items
|
||||
for categoryName in profile.hierarchy:
|
||||
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
|
||||
container[0][profile.shortName] = profile
|
||||
|
||||
# Single items, no parent
|
||||
msw = 'wxMSW' in wx.PlatformInfo
|
||||
for profile in self.singles:
|
||||
sub.Append(self.addProfile(rootMenu if msw else sub, profile))
|
||||
# Category as menu item - expands further
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
|
||||
# Items that have a parent
|
||||
for menuName, profiles in list(self.subMenus.items()):
|
||||
# Create parent item for root menu that is simply name of parent
|
||||
item = wx.MenuItem(rootMenu, ContextMenuUnconditional.nextID(), menuName)
|
||||
def makeMenu(container, parentMenu, first=False):
|
||||
menu = wx.Menu()
|
||||
if first:
|
||||
idealProfile = TargetProfile.getIdeal()
|
||||
mitem = self._addProfile(rootMenu if msw else parentMenu, idealProfile, idealProfile.fullName)
|
||||
menu.Append(mitem)
|
||||
for name, pattern in container[0].items():
|
||||
menuItem = self._addProfile(rootMenu if msw else parentMenu, pattern, name)
|
||||
menu.Append(menuItem)
|
||||
for name, subcontainer in container[1].items():
|
||||
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
|
||||
subMenu = makeMenu(subcontainer, menu)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
menu.Append(menuItem)
|
||||
menu.Bind(wx.EVT_MENU, self.handleProfileAdd)
|
||||
return menu
|
||||
|
||||
# Create menu for child items
|
||||
grandSub = wx.Menu()
|
||||
|
||||
# Apply child menu to parent item
|
||||
item.SetSubMenu(grandSub)
|
||||
|
||||
# Append child items to child menu
|
||||
for profile in profiles:
|
||||
grandSub.Append(self.addProfile(rootMenu if msw else grandSub, profile))
|
||||
sub.Append(item) # finally, append parent item to root menu
|
||||
|
||||
return sub
|
||||
subMenu = makeMenu(items, rootMenu, first=True)
|
||||
return subMenu
|
||||
|
||||
|
||||
TargetProfileAdder.register()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from graphs.wrapper import TargetWrapper
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from gui.targetProfileEditor import TargetProfileEditor
|
||||
@@ -17,7 +16,7 @@ class TargetProfileEditorMenu(ContextMenuSingle):
|
||||
return False
|
||||
if not mainItem.isProfile:
|
||||
return False
|
||||
if mainItem.item is TargetProfile.getIdeal():
|
||||
if mainItem.item.builtin:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections import OrderedDict
|
||||
from itertools import chain
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
@@ -6,6 +7,7 @@ import wx
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from gui.utils.sorter import smartSort
|
||||
from service.fit import Fit
|
||||
from service.targetProfile import TargetProfile as svc_TargetProfile
|
||||
|
||||
@@ -16,21 +18,19 @@ class TargetProfileSwitcher(ContextMenuUnconditional):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext != 'firepowerViewFull':
|
||||
if srcContext != 'firepowerViewFull':
|
||||
return False
|
||||
|
||||
sTR = svc_TargetProfile.getInstance()
|
||||
self.profiles = sTR.getTargetProfileList()
|
||||
self.profiles.sort(key=lambda p: (p.name in ['None'], p.name))
|
||||
|
||||
return len(self.profiles) > 0
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
# We always show "No Profile" anyway
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
# We take into consideration just target resists, so call menu item accordingly
|
||||
return 'Target Resists'
|
||||
|
||||
def handleResistSwitch(self, event):
|
||||
profile = self.profileIds.get(event.Id, False)
|
||||
profile = self.profileEventMap.get(event.Id, False)
|
||||
if profile is False:
|
||||
event.Skip()
|
||||
return
|
||||
@@ -40,77 +40,62 @@ class TargetProfileSwitcher(ContextMenuUnconditional):
|
||||
sFit.setTargetProfile(fitID, profile)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
|
||||
|
||||
def addProfile(self, rootMenu, profile):
|
||||
def _addProfile(self, parentMenu, profile, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
name = getattr(profile, '_name', profile.name) if profile is not None else 'No Profile'
|
||||
|
||||
self.profileIds[id] = profile
|
||||
item = wx.MenuItem(rootMenu, id, name, kind=wx.ITEM_CHECK)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, item)
|
||||
self.profileEventMap[id] = profile
|
||||
menuItem = wx.MenuItem(parentMenu, id, name, kind=wx.ITEM_CHECK)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, menuItem)
|
||||
|
||||
# determine active profile
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
f = sFit.getFit(fitID)
|
||||
tr = f.targetProfile
|
||||
checked = sFit.getFit(fitID).targetProfile is profile
|
||||
return menuItem, checked
|
||||
|
||||
checked = tr == profile
|
||||
|
||||
return item, checked
|
||||
def _addCategory(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
self.profileIds = {}
|
||||
self.subMenus = OrderedDict()
|
||||
self.singles = []
|
||||
sTR = svc_TargetProfile.getInstance()
|
||||
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
|
||||
profiles.sort(key=lambda p: smartSort(p.fullName))
|
||||
|
||||
sub = wx.Menu()
|
||||
for profile in self.profiles:
|
||||
start, end = profile.name.find('['), profile.name.find(']')
|
||||
if start is not -1 and end is not -1:
|
||||
currBase = profile.name[start + 1:end]
|
||||
name = profile.name[end + 1:].strip()
|
||||
if not name:
|
||||
self.singles.append(profile)
|
||||
continue
|
||||
# set helper attr
|
||||
setattr(profile, '_name', name)
|
||||
if currBase not in self.subMenus:
|
||||
self.subMenus[currBase] = []
|
||||
self.subMenus[currBase].append(profile)
|
||||
else:
|
||||
self.singles.append(profile)
|
||||
# Add reset
|
||||
msw = 'wxMSW' in wx.PlatformInfo
|
||||
mitem, checked = self.addProfile(rootMenu if msw else sub, None)
|
||||
sub.Append(mitem)
|
||||
mitem.Check(checked)
|
||||
sub.AppendSeparator()
|
||||
self.profileEventMap = {}
|
||||
items = (OrderedDict(), OrderedDict())
|
||||
for profile in profiles:
|
||||
container = items
|
||||
for categoryName in profile.hierarchy:
|
||||
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
|
||||
container[0][profile.shortName] = profile
|
||||
|
||||
# Single items, no parent
|
||||
for profile in self.singles:
|
||||
mitem, checked = self.addProfile(rootMenu if msw else sub, profile)
|
||||
sub.Append(mitem)
|
||||
mitem.Check(checked)
|
||||
# Category as menu item - expands further
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
|
||||
# Items that have a parent
|
||||
for menuName, profiles in list(self.subMenus.items()):
|
||||
# Create parent item for root menu that is simply name of parent
|
||||
item = wx.MenuItem(rootMenu, ContextMenuUnconditional.nextID(), menuName)
|
||||
|
||||
# Create menu for child items
|
||||
grandSub = wx.Menu()
|
||||
|
||||
# Apply child menu to parent item
|
||||
item.SetSubMenu(grandSub)
|
||||
|
||||
# Append child items to child menu
|
||||
for profile in profiles:
|
||||
mitem, checked = self.addProfile(rootMenu if msw else grandSub, profile)
|
||||
grandSub.Append(mitem)
|
||||
def makeMenu(container, parentMenu, first=False):
|
||||
menu = wx.Menu()
|
||||
if first:
|
||||
mitem, checked = self._addProfile(rootMenu if msw else parentMenu, None, 'No Profile')
|
||||
menu.Append(mitem)
|
||||
mitem.Check(checked)
|
||||
sub.Append(item) # finally, append parent item to root menu
|
||||
if len(container[0]) > 0 or len(container[1]) > 0:
|
||||
menu.AppendSeparator()
|
||||
for name, pattern in container[0].items():
|
||||
menuItem, checked = self._addProfile(rootMenu if msw else parentMenu, pattern, name)
|
||||
menu.Append(menuItem)
|
||||
menuItem.Check(checked)
|
||||
for name, subcontainer in container[1].items():
|
||||
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
|
||||
subMenu = makeMenu(subcontainer, menu)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
menu.Append(menuItem)
|
||||
menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
|
||||
return menu
|
||||
|
||||
return sub
|
||||
subMenu = makeMenu(items, rootMenu, first=True)
|
||||
return subMenu
|
||||
|
||||
|
||||
TargetProfileSwitcher.register()
|
||||
|
||||
@@ -10,12 +10,13 @@ _ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
|
||||
|
||||
|
||||
class AttributeSliderChangeEvent:
|
||||
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
|
||||
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=True):
|
||||
self.__obj = obj
|
||||
self.__old = old_value
|
||||
self.__new = new_value
|
||||
self.__old_percent = old_percentage
|
||||
self.__new_percent = new_percentage
|
||||
self.__affect_modified_flag = affect_modified_flag
|
||||
|
||||
def GetObj(self):
|
||||
return self.__obj
|
||||
@@ -32,6 +33,10 @@ class AttributeSliderChangeEvent:
|
||||
def GetPercentage(self):
|
||||
return self.__new_percent
|
||||
|
||||
@property
|
||||
def AffectsModifiedFlag(self):
|
||||
return self.__affect_modified_flag
|
||||
|
||||
Object = property(GetObj)
|
||||
OldValue = property(GetOldValue)
|
||||
Value = property(GetValue)
|
||||
@@ -40,9 +45,9 @@ class AttributeSliderChangeEvent:
|
||||
|
||||
|
||||
class ValueChanged(_ValueChanged, AttributeSliderChangeEvent):
|
||||
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
|
||||
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=True):
|
||||
_ValueChanged.__init__(self)
|
||||
AttributeSliderChangeEvent.__init__(self, obj, old_value, new_value, old_percentage, new_percentage)
|
||||
AttributeSliderChangeEvent.__init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=affect_modified_flag)
|
||||
|
||||
|
||||
class AttributeSlider(wx.Panel):
|
||||
@@ -118,16 +123,19 @@ class AttributeSlider(wx.Panel):
|
||||
self.SetValue(self.GetValue())
|
||||
evt.Skip()
|
||||
|
||||
def SetValue(self, value, post_event=True):
|
||||
def SetValue(self, value, post_event=True, affect_modified_flag=True):
|
||||
self.ctrl.SetValue(value)
|
||||
invert_factor = -1 if self.inverse else 1
|
||||
if value >= self.base_value:
|
||||
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
||||
else:
|
||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
|
||||
try:
|
||||
if value >= self.base_value:
|
||||
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
|
||||
else:
|
||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
|
||||
except ZeroDivisionError:
|
||||
slider_percentage = 0
|
||||
self.slider.SetValue(slider_percentage)
|
||||
if post_event:
|
||||
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage))
|
||||
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage, affect_modified_flag=affect_modified_flag))
|
||||
|
||||
def OnMouseWheel(self, evt):
|
||||
if evt.GetWheelRotation() > 0 and evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:
|
||||
|
||||
@@ -112,6 +112,8 @@ class ItemAffectedBy(wx.Panel):
|
||||
else:
|
||||
try:
|
||||
self.affectedBy.CollapseAll()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -171,7 +173,7 @@ class ItemAffectedBy(wx.Panel):
|
||||
|
||||
def sortAttrDisplayName(self, attr):
|
||||
info = self.stuff.item.attributes.get(attr)
|
||||
if info and info.displayName != "":
|
||||
if info and info.displayName:
|
||||
return info.displayName
|
||||
|
||||
return attr
|
||||
@@ -251,7 +253,7 @@ class ItemAffectedBy(wx.Panel):
|
||||
|
||||
for attrName in attrOrder:
|
||||
attrInfo = self.stuff.item.attributes.get(attrName)
|
||||
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName
|
||||
displayName = attrInfo.displayName if attrInfo and attrInfo.displayName else attrName
|
||||
|
||||
if attrInfo:
|
||||
if attrInfo.iconID is not None:
|
||||
@@ -444,7 +446,7 @@ class ItemAffectedBy(wx.Panel):
|
||||
attrModifier = "-"
|
||||
attrAmount = -attrAmount
|
||||
|
||||
attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier,
|
||||
attributes.append((attrName, (displayName if displayName else attrName), attrModifier,
|
||||
attrAmount, penalized, attrIcon))
|
||||
|
||||
attrSorted = sorted(attributes, key=lambda attribName: attribName[0])
|
||||
@@ -454,14 +456,14 @@ class ItemAffectedBy(wx.Panel):
|
||||
if self.showRealNames:
|
||||
display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)
|
||||
saved = "%s %s %.2f %s" % (
|
||||
displayName if displayName != "" else attrName,
|
||||
displayName if displayName else attrName,
|
||||
attrModifier,
|
||||
attrAmount,
|
||||
penalized
|
||||
)
|
||||
else:
|
||||
display = "%s %s %.2f %s" % (
|
||||
displayName if displayName != "" else attrName,
|
||||
displayName if displayName else attrName,
|
||||
attrModifier,
|
||||
attrAmount,
|
||||
penalized
|
||||
|
||||
@@ -35,7 +35,7 @@ class ItemDescription(wx.Panel):
|
||||
self.Layout()
|
||||
|
||||
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.description.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.description.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
self.popupMenu = wx.Menu()
|
||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||
@@ -50,7 +50,7 @@ class ItemDescription(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -76,6 +76,7 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
|
||||
self.mod = mod
|
||||
self.timer = None
|
||||
self.isModified = False
|
||||
|
||||
goodColor = wx.Colour(96, 191, 0)
|
||||
badColor = wx.Colour(255, 64, 0)
|
||||
@@ -92,6 +93,8 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
|
||||
first = True
|
||||
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||
if m.baseValue == 0:
|
||||
continue
|
||||
if not first:
|
||||
sizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||
first = False
|
||||
@@ -171,6 +174,8 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
self.SetSizer(sizer)
|
||||
|
||||
def changeMutatedValue(self, evt):
|
||||
if evt.AffectsModifiedFlag:
|
||||
self.isModified = True
|
||||
m = self.event_mapping[evt.Object]
|
||||
value = evt.Value
|
||||
value = m.attribute.unit.ComplicateValue(value)
|
||||
@@ -188,29 +193,32 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
self.timer = wx.CallLater(1000, self.callLater)
|
||||
|
||||
def resetMutatedValues(self, evt):
|
||||
self.isModified = True
|
||||
sFit = Fit.getInstance()
|
||||
for slider, m in self.event_mapping.items():
|
||||
value = sFit.changeMutatedValuePrelim(m, m.baseValue)
|
||||
value = m.attribute.unit.SimplifyValue(value)
|
||||
slider.SetValue(value)
|
||||
slider.SetValue(value, affect_modified_flag=False)
|
||||
evt.Skip()
|
||||
|
||||
def randomMutatedValues(self, evt):
|
||||
self.isModified = True
|
||||
sFit = Fit.getInstance()
|
||||
for slider, m in self.event_mapping.items():
|
||||
value = random.uniform(m.minValue, m.maxValue)
|
||||
value = sFit.changeMutatedValuePrelim(m, value)
|
||||
value = m.attribute.unit.SimplifyValue(value)
|
||||
slider.SetValue(value)
|
||||
slider.SetValue(value, affect_modified_flag=False)
|
||||
evt.Skip()
|
||||
|
||||
def revertChanges(self, evt):
|
||||
self.isModified = False
|
||||
sFit = Fit.getInstance()
|
||||
for slider, m in self.event_mapping.items():
|
||||
if m.attrID in self.initialMutations:
|
||||
value = sFit.changeMutatedValuePrelim(m, self.initialMutations[m.attrID])
|
||||
value = m.attribute.unit.SimplifyValue(value)
|
||||
slider.SetValue(value)
|
||||
slider.SetValue(value, affect_modified_flag=False)
|
||||
evt.Skip()
|
||||
|
||||
def OnWindowClose(self):
|
||||
@@ -218,15 +226,18 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.carryingFitID)
|
||||
if self.mod in fit.modules:
|
||||
currentMutation = {}
|
||||
for slider, m in self.event_mapping.items():
|
||||
# Sliders may have more up-to-date info than mutator in case we changed
|
||||
# value in slider and without confirming it, decided to close window
|
||||
value = slider.GetValue()
|
||||
value = m.attribute.unit.ComplicateValue(value)
|
||||
if value != m.value:
|
||||
value = sFit.changeMutatedValuePrelim(m, value)
|
||||
currentMutation[m.attrID] = value
|
||||
if self.isModified:
|
||||
currentMutation = {}
|
||||
for slider, m in self.event_mapping.items():
|
||||
# Sliders may have more up-to-date info than mutator in case we changed
|
||||
# value in slider and without confirming it, decided to close window
|
||||
value = slider.GetValue()
|
||||
value = m.attribute.unit.ComplicateValue(value)
|
||||
if value != m.value:
|
||||
value = sFit.changeMutatedValuePrelim(m, value)
|
||||
currentMutation[m.attrID] = value
|
||||
else:
|
||||
currentMutation = self.initialMutations
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand(
|
||||
fitID=self.carryingFitID,
|
||||
|
||||
@@ -86,6 +86,8 @@ class ItemProperties(wx.Panel):
|
||||
valueUnit = str(value)
|
||||
|
||||
self.paramList.SetItem(index, 1, valueUnit)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# TODO: Add logging to this.
|
||||
# We couldn't get a property for some reason. Skip it for now.
|
||||
|
||||
@@ -14,7 +14,7 @@ class ItemTraits(wx.Panel):
|
||||
self.traits.SetPage(item.traits.traitText)
|
||||
|
||||
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.traits.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.traits.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.Layout()
|
||||
@@ -32,7 +32,7 @@ class ItemTraits(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from eos.saveddata.module import Module
|
||||
import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
from config import slotColourMap
|
||||
from eos.saveddata.module import Module
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.display import Display
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.attribute import Attribute
|
||||
from service.fit import Fit
|
||||
from config import slotColourMap
|
||||
from service.market import Market
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -49,8 +50,6 @@ class ItemView(Display):
|
||||
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
|
||||
# Make reverse map, used by sorter
|
||||
self.metaMap = self.makeReverseMetaMap()
|
||||
self.active = []
|
||||
|
||||
def delaySearch(self, evt):
|
||||
@@ -172,15 +171,15 @@ class ItemView(Display):
|
||||
def scheduleSearch(self, event=None):
|
||||
self.searchTimer.Stop() # Cancel any pending timers
|
||||
search = self.marketBrowser.search.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Re-select market group if search query has zero length
|
||||
if len(realsearch) == 0:
|
||||
self.selectionMade('search')
|
||||
return
|
||||
|
||||
self.marketBrowser.mode = 'search'
|
||||
self.sMkt.searchItems(search, self.populateSearch)
|
||||
self.sMkt.searchItems(search, self.populateSearch, 'market')
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
# Wipe item store and update everything to accomodate with it
|
||||
@@ -195,30 +194,15 @@ class ItemView(Display):
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
# If we're no longer searching, dump the results
|
||||
if self.marketBrowser.mode != 'search':
|
||||
return
|
||||
items = Market.getItems(itemIDs)
|
||||
self.updateItemStore(items)
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def itemSort(self, item):
|
||||
sMkt = self.sMkt
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = self.metaMap.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
def contextMenu(self, event):
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
self.ensureSelection(clickedPos)
|
||||
@@ -241,7 +225,7 @@ class ItemView(Display):
|
||||
self.unselectAll()
|
||||
# Perform sorting, using item's meta levels besides other stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
# Mark current item list as active
|
||||
self.active = items
|
||||
# Show them
|
||||
@@ -251,26 +235,12 @@ class ItemView(Display):
|
||||
if len(items) > 1:
|
||||
# Re-sort stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
for i, item in enumerate(items[:9]):
|
||||
# set shortcut info for first 9 modules
|
||||
item.marketShortcut = i + 1
|
||||
|
||||
Display.refresh(self, items)
|
||||
|
||||
def makeReverseMetaMap(self):
|
||||
"""
|
||||
Form map which tells in which tab items of given metagroup are located
|
||||
"""
|
||||
revmap = {}
|
||||
i = 0
|
||||
for mgids in self.sMkt.META_MAP.values():
|
||||
for mgid in mgids:
|
||||
revmap[mgid] = i
|
||||
i += 1
|
||||
return revmap
|
||||
|
||||
def columnBackground(self, colItem, item):
|
||||
if self.sFit.serviceFittingOptions["colorFitBySlot"]:
|
||||
return slotColourMap.get(Module.calculateSlot(item)) or self.GetBackgroundColour()
|
||||
|
||||
@@ -60,9 +60,12 @@ class MarketTree(wx.TreeCtrl):
|
||||
# If market should have items but it doesn't, do not show it
|
||||
if sMkt.marketGroupValidityCheck(childMktGrp) is False:
|
||||
continue
|
||||
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
|
||||
icon = sMkt.getIconByMarketGroup(childMktGrp)
|
||||
iconId = -1 if icon is None else self.addImage(icon)
|
||||
try:
|
||||
childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.debug("Error appending item.")
|
||||
pyfalog.debug(e)
|
||||
|
||||
@@ -86,7 +86,7 @@ class PFSearchBox(wx.Window):
|
||||
|
||||
def OnKeyPress(self, event):
|
||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
|
||||
@@ -21,9 +21,12 @@ class PFContextMenuPref(PreferenceView):
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
|
||||
"Disabling context menus can improve responsiveness.",
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stSubTitle = wx.StaticText(
|
||||
panel, wx.ID_ANY,
|
||||
'Disabling context menus can improve responsiveness.\n'
|
||||
'You can hold {} key + right-click to show all menu items regardless of these settings.'.format(
|
||||
'Command' if 'wxMac' in wx.PlatformInfo else 'Control'),
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stSubTitle.Wrap(-1)
|
||||
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 5)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user