Compare commits
267 Commits
v2.13.1dev
...
v2.17.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10583fd506 | ||
|
|
713694be56 | ||
|
|
f50293cf77 | ||
|
|
7c88fa477f | ||
|
|
f8df540fad | ||
|
|
61a01805cc | ||
|
|
0f74c97fbf | ||
|
|
29713b69dc | ||
|
|
cc3c2cb9c8 | ||
|
|
b36a3959da | ||
|
|
3b26bfc0e9 | ||
|
|
c59b621963 | ||
|
|
977a8fa329 | ||
|
|
a5226fee83 | ||
|
|
e4069a3988 | ||
|
|
6527f9e11e | ||
|
|
9ddfcc894f | ||
|
|
f22a4f13e5 | ||
|
|
fc9337df67 | ||
|
|
98a8332cfa | ||
|
|
a78eedad48 | ||
|
|
85e1a1bd06 | ||
|
|
25b2c04309 | ||
|
|
54a84d0e42 | ||
|
|
af0a4d1def | ||
|
|
97cdde84ce | ||
|
|
a65129b277 | ||
|
|
3b40a49918 | ||
|
|
d95345b476 | ||
|
|
2ba2d95017 | ||
|
|
4d0ffedcc8 | ||
|
|
84abde4fc5 | ||
|
|
8897f1e4b1 | ||
|
|
6f33cacb7a | ||
|
|
b1a8c0ad09 | ||
|
|
13ed635803 | ||
|
|
5a44909ebf | ||
|
|
6cbb80693d | ||
|
|
0b90d254f9 | ||
|
|
641d36205c | ||
|
|
6e38b6ea4d | ||
|
|
84b4d8fabb | ||
|
|
c0e1f7e746 | ||
|
|
d734c12168 | ||
|
|
340d94eb3e | ||
|
|
9b0b31648c | ||
|
|
8346358c37 | ||
|
|
b935700a12 | ||
|
|
2f4a5a4830 | ||
|
|
e951ce8e9d | ||
|
|
fc6d42f483 | ||
|
|
7df1431ad2 | ||
|
|
4d4680961e | ||
|
|
1cae99b812 | ||
|
|
3727d19311 | ||
|
|
c45c093f4f | ||
|
|
b613d3e312 | ||
|
|
4cff9247b0 | ||
|
|
b9a26ec28d | ||
|
|
245f81e888 | ||
|
|
204c1ba9f2 | ||
|
|
a7a4be133c | ||
|
|
14f9a46d7a | ||
|
|
003dd040dc | ||
|
|
97cf53217e | ||
|
|
7c1805826d | ||
|
|
d6a2b4dfc4 | ||
|
|
d70952520a | ||
|
|
a57867b806 | ||
|
|
17d7583cfc | ||
|
|
55ad95081b | ||
|
|
da8da1759f | ||
|
|
b4e758b9ee | ||
|
|
3ba41db699 | ||
|
|
cfc47cf483 | ||
|
|
38c8be995b | ||
|
|
492207700b | ||
|
|
b69adefbf8 | ||
|
|
361299f51e | ||
|
|
025091c3f6 | ||
|
|
a54d70036b | ||
|
|
9e67b5962c | ||
|
|
ed0c080163 | ||
|
|
21389d84fc | ||
|
|
0fe54631ad | ||
|
|
36ea5200f2 | ||
|
|
d9934160a6 | ||
|
|
b9c92c48d3 | ||
|
|
6d23df6156 | ||
|
|
a913ab72c2 | ||
|
|
f6d33a2ac1 | ||
|
|
5a3fec33a7 | ||
|
|
95734aca2c | ||
|
|
377254be9d | ||
|
|
ad9905a5e0 | ||
|
|
6fc5a9699e | ||
|
|
0ee86f4836 | ||
|
|
85593367f7 | ||
|
|
465ea61b06 | ||
|
|
2d5507e951 | ||
|
|
84e20be153 | ||
|
|
60080c02b0 | ||
|
|
2a0d4179d0 | ||
|
|
6dd55e0cd4 | ||
|
|
0adc6e0c6f | ||
|
|
e155356701 | ||
|
|
ef44b50980 | ||
|
|
6785d16c87 | ||
|
|
76c670b7d5 | ||
|
|
717303a72f | ||
|
|
e13125b174 | ||
|
|
8aa7964b81 | ||
|
|
5e5f23d296 | ||
|
|
05837f99ff | ||
|
|
70f620c2c3 | ||
|
|
10ffb987ea | ||
|
|
13ed785551 | ||
|
|
69d2e38647 | ||
|
|
c45abbdbcf | ||
|
|
2655b001a9 | ||
|
|
3c7f4edb1b | ||
|
|
663cbab401 | ||
|
|
897ca9ca43 | ||
|
|
b31a071859 | ||
|
|
8c2788fd78 | ||
|
|
dee8fa400c | ||
|
|
2339327473 | ||
|
|
f6f9239dd5 | ||
|
|
cd4c8c8c10 | ||
|
|
c25eda8b64 | ||
|
|
b6edf0e034 | ||
|
|
af4277fc7e | ||
|
|
acd774abe5 | ||
|
|
9081353634 | ||
|
|
9c7fa37a72 | ||
|
|
d92e11893a | ||
|
|
f55ab00bf5 | ||
|
|
36265aa2a3 | ||
|
|
bfedcdbffd | ||
|
|
a5d10c4a76 | ||
|
|
2962ce1945 | ||
|
|
0063d2955e | ||
|
|
9787a18666 | ||
|
|
d8cd3197b5 | ||
|
|
e07c4f65ab | ||
|
|
c3108f773a | ||
|
|
a400821268 | ||
|
|
ca4bac07da | ||
|
|
0038eacc3f | ||
|
|
4bd633a0d4 | ||
|
|
ee837f0b04 | ||
|
|
016e18cc89 | ||
|
|
1d6ac53183 | ||
|
|
1e32dfa463 | ||
|
|
4431753570 | ||
|
|
6fdb57318c | ||
|
|
649db019de | ||
|
|
1d528be0ef | ||
|
|
2d6f6df83d | ||
|
|
7fa998f276 | ||
|
|
6a3157a4c8 | ||
|
|
74efa50576 | ||
|
|
e99be78c54 | ||
|
|
e386232de1 | ||
|
|
a15896044d | ||
|
|
72c74513ce | ||
|
|
34d6d13cb2 | ||
|
|
323bb8e064 | ||
|
|
b628f8ef9b | ||
|
|
1900216d36 | ||
|
|
c045ed81c1 | ||
|
|
39edec60e3 | ||
|
|
259214e907 | ||
|
|
2e1c53392d | ||
|
|
7a77d12686 | ||
|
|
763a7714ed | ||
|
|
e89f35d654 | ||
|
|
3196751b7a | ||
|
|
f221024974 | ||
|
|
66cab8a0d4 | ||
|
|
daa49a8cd4 | ||
|
|
b8601ff240 | ||
|
|
5df3eeca64 | ||
|
|
274e4ac2ca | ||
|
|
f6485251ca | ||
|
|
c03fb70def | ||
|
|
8652a2891b | ||
|
|
a09af0a9eb | ||
|
|
c7d4b93fba | ||
|
|
745c0db530 | ||
|
|
aa56ab8d6c | ||
|
|
043c430221 | ||
|
|
dc5cc5855e | ||
|
|
59d6266e2b | ||
|
|
7495ba67f8 | ||
|
|
e649683a4d | ||
|
|
bec58a5772 | ||
|
|
e8f9ae8a9c | ||
|
|
679382e220 | ||
|
|
7f86782f54 | ||
|
|
f80b7d972f | ||
|
|
c0bd489c1b | ||
|
|
41e4c2107d | ||
|
|
cfc95c272a | ||
|
|
f778f9ceae | ||
|
|
7fb6170bcb | ||
|
|
fa37428cd3 | ||
|
|
2a2d9d3456 | ||
|
|
a91efb681f | ||
|
|
386e05be8f | ||
|
|
65e7bf609d | ||
|
|
7a58d97652 | ||
|
|
c5118da417 | ||
|
|
13b505525d | ||
|
|
b682dec363 | ||
|
|
6aa98e2214 | ||
|
|
8fba988222 | ||
|
|
24a82efe50 | ||
|
|
8054fa9267 | ||
|
|
a0e39a3725 | ||
|
|
185d6d0c51 | ||
|
|
1975e96848 | ||
|
|
ab37d228ea | ||
|
|
f48483d754 | ||
|
|
e6cfd33435 | ||
|
|
c29126ce1d | ||
|
|
c52170b731 | ||
|
|
6607dd31bf | ||
|
|
c6c74be38d | ||
|
|
41c6062ff9 | ||
|
|
bbb96a73b7 | ||
|
|
9eb3b9e017 | ||
|
|
6b3e94729c | ||
|
|
fb48f2b5d4 | ||
|
|
cfffc77777 | ||
|
|
f7089f358d | ||
|
|
06c4f2ce46 | ||
|
|
83eb0abd92 | ||
|
|
4199b33c47 | ||
|
|
23cd4bff5a | ||
|
|
b65f95fe77 | ||
|
|
32160c94e1 | ||
|
|
ac02fba98b | ||
|
|
cde0108cba | ||
|
|
39dc7e4a46 | ||
|
|
9943f784a8 | ||
|
|
88ce45f29e | ||
|
|
7157e876ca | ||
|
|
0cf88cf7ca | ||
|
|
10dfdc3627 | ||
|
|
76bdefcda6 | ||
|
|
1c2c8cc5f9 | ||
|
|
58f853de5b | ||
|
|
c052297bf7 | ||
|
|
9e78cd1076 | ||
|
|
79f4deacea | ||
|
|
ff42c4c711 | ||
|
|
02d31d49d8 | ||
|
|
64f47fcc24 | ||
|
|
0ceb8acd64 | ||
|
|
78579e2e13 | ||
|
|
cf4e1d3935 | ||
|
|
d1be0bb680 | ||
|
|
384d9f4614 | ||
|
|
47434c68f9 | ||
|
|
af88afb6b5 | ||
|
|
b2c718d614 |
121
.appveyor.yml
121
.appveyor.yml
@@ -1,124 +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:"
|
||||
|
||||
##########
|
||||
# PyInstaller - create binaries for pyfa
|
||||
##########
|
||||
- ps: echo("Build pyfa:")
|
||||
# Build gamedata DB
|
||||
- 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
|
||||
|
||||
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)
|
||||
@@ -126,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,16 +3,16 @@ 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:
|
||||
- python3 db_update.py
|
||||
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
|
||||
- bash scripts/package-osx.sh
|
||||
before_deploy:
|
||||
@@ -21,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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- Python 3.6
|
||||
- Git CLI installed
|
||||
- Python, pip and git are all available as command-line commands (add to path if needed)
|
||||
- Python, pip and git are all available as command-line commands (add to the path if needed)
|
||||
|
||||
Virtual environment will be created in *PyfaEnv* folder. Project will be cloned and run from the *PyfaDEV* folder. Separate virtual environment will be created so required libraries won't clutter the main python installation.
|
||||
|
||||
@@ -12,21 +12,21 @@ Virtual environment will be created in *PyfaEnv* folder. Project will be cloned
|
||||
|
||||
## Setting up the project manually
|
||||
|
||||
Clone the repo
|
||||
Clone the repository
|
||||
```
|
||||
git clone <repo> PyfaDEV
|
||||
```
|
||||
|
||||
Create virtual environment
|
||||
Create the virtual environment
|
||||
```
|
||||
python -m venv PyfaEnv
|
||||
```
|
||||
|
||||
Activate virtual environment
|
||||
Activate the virtual environment
|
||||
|
||||
```
|
||||
For cmd.exe: PyfaEnv\scripts\activate.bat
|
||||
For bash: source <venv>/bin/activate
|
||||
For bash: source <venv>/Scripts/activate
|
||||
```
|
||||
> For other OS check [Python documentation](https://docs.python.org/3/library/venv.html)
|
||||
|
||||
@@ -36,7 +36,7 @@ pip install -r PyfaDEV\requirements.txt
|
||||
```
|
||||
> For some Linux distributions, you may need to install separate wxPython bindings, such as `python-matplotlib-wx`
|
||||
|
||||
Check that libs from *requirements.txt* are installed
|
||||
Check that the libs from *requirements.txt* are installed
|
||||
```
|
||||
pip list
|
||||
```
|
||||
@@ -52,11 +52,11 @@ python PyfaDEV\pyfa.py
|
||||
|
||||
Install PyCharm / Other IntelliJ product with Python plugin
|
||||
|
||||
After launching select *Check out from Version Control* -> *GIt*
|
||||
After launching - select *Check out from Version Control* -> *GIt*
|
||||
|
||||

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

|
||||
|
||||
@@ -68,7 +68,7 @@ Press on options and add new virtual environment.
|
||||
|
||||

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

|
||||
|
||||
@@ -76,4 +76,27 @@ Create new *Run Configuration*. Set correct *Script path* and *Python interprete
|
||||
|
||||

|
||||
|
||||
Test that the project is starting properly.
|
||||
Check that the project is starting properly.
|
||||
|
||||
## Running tests
|
||||
|
||||
Switch to the proper virtual environment
|
||||
```
|
||||
For cmd.exe: PyfaEnv\scripts\activate.bat
|
||||
For bash: source <venv>/Scripts/activate
|
||||
```
|
||||
|
||||
Install pytest
|
||||
```
|
||||
pip install pytest
|
||||
```
|
||||
|
||||
Switch to pyfa directory.
|
||||
|
||||
Run tests (any will do)
|
||||
```
|
||||
python -m pytest
|
||||
py.test
|
||||
```
|
||||
|
||||
More information on tests can be found on appropriate [Wiki page](https://github.com/pyfa-org/Pyfa/wiki/Developers:-Writing-Tests-for-Pyfa).
|
||||
@@ -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:":
|
||||
|
||||
@@ -39,6 +39,8 @@ loggingLevel = None
|
||||
logging_setup = None
|
||||
cipher = None
|
||||
clientHash = None
|
||||
experimentalFeatures = None
|
||||
version = None
|
||||
|
||||
ESI_CACHE = 'esi_cache'
|
||||
|
||||
@@ -103,6 +105,7 @@ def defPaths(customSavePath=None):
|
||||
global cipher
|
||||
global clientHash
|
||||
global version
|
||||
global experimentalFeatures
|
||||
|
||||
pyfalog.debug("Configuring Pyfa")
|
||||
|
||||
@@ -168,6 +171,10 @@ def defPaths(customSavePath=None):
|
||||
|
||||
logPath = os.path.join(savePath, logFile)
|
||||
|
||||
experimentalFeatures = getattr(configforced, "experimentalFeatures", experimentalFeatures)
|
||||
if experimentalFeatures is None:
|
||||
experimentalFeatures = False
|
||||
|
||||
# DON'T MODIFY ANYTHING BELOW
|
||||
import eos.config
|
||||
|
||||
@@ -226,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([
|
||||
|
||||
510
db_update.py
Normal file
510
db_update.py
Normal file
@@ -0,0 +1,510 @@
|
||||
#!/usr/bin/env python3
|
||||
#======================================================================
|
||||
# Copyright (C) 2012 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
#
|
||||
# eos is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
#======================================================================
|
||||
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
|
||||
ROOT_DIR = os.path.realpath(os.path.dirname(__file__))
|
||||
DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
|
||||
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
|
||||
if ROOT_DIR not in sys.path:
|
||||
sys.path.insert(0, ROOT_DIR)
|
||||
GAMEDATA_SCHEMA_VERSION = 3
|
||||
|
||||
|
||||
def db_needs_update():
|
||||
"""True if needs, false if it does not, none if we cannot check it."""
|
||||
try:
|
||||
with open(os.path.join(JSON_DIR, 'phobos', 'metadata.json')) as f:
|
||||
data_version = next((r['field_value'] for r in json.load(f) if r['field_name'] == 'client_build'))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
# If we have no source data - return None; should not update in this case
|
||||
except:
|
||||
return None
|
||||
if not os.path.isfile(DB_PATH):
|
||||
print('Gamedata DB not found')
|
||||
return True
|
||||
db_data_version = None
|
||||
db_schema_version = None
|
||||
try:
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
cursor = db.cursor()
|
||||
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'client_build\'')
|
||||
for row in cursor:
|
||||
db_data_version = int(row[0])
|
||||
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'schema_version\'')
|
||||
for row in cursor:
|
||||
db_schema_version = int(row[0])
|
||||
cursor.close()
|
||||
db.close()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print('Error when fetching gamedata DB metadata')
|
||||
return True
|
||||
if data_version != db_data_version:
|
||||
print('Gamedata DB data version mismatch: needed {}, DB has {}'.format(data_version, db_data_version))
|
||||
return True
|
||||
if GAMEDATA_SCHEMA_VERSION != db_schema_version:
|
||||
print('Gamedata DB schema version mismatch: needed {}, DB has {}'.format(GAMEDATA_SCHEMA_VERSION, db_schema_version))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def update_db():
|
||||
|
||||
print('Building gamedata DB...')
|
||||
|
||||
if os.path.isfile(DB_PATH):
|
||||
os.remove(DB_PATH)
|
||||
|
||||
import eos.db
|
||||
import eos.gamedata
|
||||
|
||||
# Create the database tables
|
||||
eos.db.gamedata_meta.create_all()
|
||||
|
||||
def _readData(minerName, jsonName, keyIdName=None):
|
||||
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
|
||||
rawData = json.load(f)
|
||||
if not keyIdName:
|
||||
return rawData
|
||||
# IDs in keys, rows in values
|
||||
data = []
|
||||
for k, v in rawData.items():
|
||||
row = {}
|
||||
row.update(v)
|
||||
if keyIdName not in row:
|
||||
row[keyIdName] = int(k)
|
||||
data.append(row)
|
||||
return data
|
||||
|
||||
def _addRows(data, cls, fieldMap=None):
|
||||
if fieldMap is None:
|
||||
fieldMap = {}
|
||||
for row in data:
|
||||
instance = cls()
|
||||
for k, v in row.items():
|
||||
if isinstance(v, str):
|
||||
v = v.strip()
|
||||
setattr(instance, fieldMap.get(k, k), v)
|
||||
eos.db.gamedata_session.add(instance)
|
||||
|
||||
def processEveTypes():
|
||||
print('processing evetypes')
|
||||
data = _readData('fsd_lite', 'evetypes', keyIdName='typeID')
|
||||
for row in data:
|
||||
if (
|
||||
# Apparently people really want Civilian modules available
|
||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
||||
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor')
|
||||
):
|
||||
row['published'] = True
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
if (
|
||||
row['published'] or
|
||||
# group Ship Modifiers, for items like tactical t3 ship modes
|
||||
row['groupID'] == 1306 or
|
||||
# Micro Bombs (Fighters)
|
||||
row['typeID'] in (41549, 41548, 41551, 41550) or
|
||||
# Abyssal weather (environment)
|
||||
row['groupID'] in (
|
||||
1882,
|
||||
1975,
|
||||
1971,
|
||||
# the "container" for the abyssal environments
|
||||
1983)
|
||||
):
|
||||
newData.append(row)
|
||||
|
||||
_addRows(newData, eos.gamedata.Item)
|
||||
return newData
|
||||
|
||||
def processEveGroups():
|
||||
print('processing evegroups')
|
||||
data = _readData('fsd_lite', 'evegroups', keyIdName='groupID')
|
||||
_addRows(data, eos.gamedata.Group)
|
||||
return data
|
||||
|
||||
def processEveCategories():
|
||||
print('processing evecategories')
|
||||
data = _readData('fsd_lite', 'evecategories', keyIdName='categoryID')
|
||||
_addRows(data, eos.gamedata.Category)
|
||||
|
||||
def processDogmaAttributes():
|
||||
print('processing dogmaattributes')
|
||||
data = _readData('fsd_binary', 'dogmaattributes', keyIdName='attributeID')
|
||||
_addRows(data, eos.gamedata.AttributeInfo)
|
||||
|
||||
def processDogmaTypeAttributes(eveTypesData):
|
||||
print('processing dogmatypeattributes')
|
||||
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||
newData = []
|
||||
for row in eveTypesData:
|
||||
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
||||
if attrName in row:
|
||||
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
||||
for typeData in data:
|
||||
if typeData['typeID'] not in eveTypeIds:
|
||||
continue
|
||||
for row in typeData.get('dogmaAttributes', ()):
|
||||
row['typeID'] = typeData['typeID']
|
||||
newData.append(row)
|
||||
_addRows(newData, eos.gamedata.Attribute)
|
||||
return newData
|
||||
|
||||
def processDynamicItemAttributes():
|
||||
print('processing dynamicitemattributes')
|
||||
data = _readData('fsd_binary', 'dynamicitemattributes')
|
||||
for mutaID, mutaData in data.items():
|
||||
muta = eos.gamedata.DynamicItem()
|
||||
muta.typeID = mutaID
|
||||
muta.resultingTypeID = mutaData['inputOutputMapping'][0]['resultingType']
|
||||
eos.db.gamedata_session.add(muta)
|
||||
|
||||
for x in mutaData['inputOutputMapping'][0]['applicableTypes']:
|
||||
item = eos.gamedata.DynamicItemItem()
|
||||
item.typeID = mutaID
|
||||
item.applicableTypeID = x
|
||||
eos.db.gamedata_session.add(item)
|
||||
|
||||
for attrID, attrData in mutaData['attributeIDs'].items():
|
||||
attr = eos.gamedata.DynamicItemAttribute()
|
||||
attr.typeID = mutaID
|
||||
attr.attributeID = attrID
|
||||
attr.min = attrData['min']
|
||||
attr.max = attrData['max']
|
||||
eos.db.gamedata_session.add(attr)
|
||||
|
||||
def processDogmaEffects():
|
||||
print('processing dogmaeffects')
|
||||
data = _readData('fsd_binary', 'dogmaeffects', keyIdName='effectID')
|
||||
_addRows(data, eos.gamedata.Effect, fieldMap={'resistanceAttributeID': 'resistanceID'})
|
||||
|
||||
def processDogmaTypeEffects(eveTypesData):
|
||||
print('processing dogmatypeeffects')
|
||||
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
|
||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||
newData = []
|
||||
for typeData in data:
|
||||
if typeData['typeID'] not in eveTypeIds:
|
||||
continue
|
||||
for row in typeData.get('dogmaEffects', ()):
|
||||
row['typeID'] = typeData['typeID']
|
||||
newData.append(row)
|
||||
_addRows(newData, eos.gamedata.ItemEffect)
|
||||
return newData
|
||||
|
||||
def processDogmaUnits():
|
||||
print('processing dogmaunits')
|
||||
data = _readData('fsd_binary', 'dogmaunits', keyIdName='unitID')
|
||||
_addRows(data, eos.gamedata.Unit, fieldMap={'name': 'unitName'})
|
||||
|
||||
def processMarketGroups():
|
||||
print('processing marketgroups')
|
||||
data = _readData('fsd_binary', 'marketgroups', keyIdName='marketGroupID')
|
||||
_addRows(data, eos.gamedata.MarketGroup, fieldMap={'name': 'marketGroupName'})
|
||||
|
||||
def processMetaGroups():
|
||||
print('processing metagroups')
|
||||
data = _readData('fsd_binary', 'metagroups', keyIdName='metaGroupID')
|
||||
_addRows(data, eos.gamedata.MetaGroup)
|
||||
|
||||
def processCloneGrades():
|
||||
print('processing clonegrades')
|
||||
data = _readData('fsd_lite', 'clonegrades')
|
||||
|
||||
newData = []
|
||||
# December, 2017 - CCP decided to use only one set of skill levels for alpha clones. However, this is still
|
||||
# represented in the data as a skillset per race. To ensure that all skills are the same, we store them in a way
|
||||
# that we can check to make sure all races have the same skills, as well as skill levels
|
||||
check = {}
|
||||
for ID in data:
|
||||
for skill in data[ID]['skills']:
|
||||
newData.append({
|
||||
'alphaCloneID': int(ID),
|
||||
'alphaCloneName': 'Alpha Clone',
|
||||
'typeID': skill['typeID'],
|
||||
'level': skill['level']})
|
||||
if ID not in check:
|
||||
check[ID] = {}
|
||||
check[ID][int(skill['typeID'])] = int(skill['level'])
|
||||
if not functools.reduce(lambda a, b: a if a == b else False, [v for _, v in check.items()]):
|
||||
raise Exception('Alpha Clones not all equal')
|
||||
newData = [x for x in newData if x['alphaCloneID'] == 1]
|
||||
if len(newData) == 0:
|
||||
raise Exception('Alpha Clone processing failed')
|
||||
|
||||
tmp = []
|
||||
for row in newData:
|
||||
if row['alphaCloneID'] not in tmp:
|
||||
cloneParent = eos.gamedata.AlphaClone()
|
||||
setattr(cloneParent, 'alphaCloneID', row['alphaCloneID'])
|
||||
setattr(cloneParent, 'alphaCloneName', row['alphaCloneName'])
|
||||
eos.db.gamedata_session.add(cloneParent)
|
||||
tmp.append(row['alphaCloneID'])
|
||||
_addRows(newData, eos.gamedata.AlphaCloneSkill)
|
||||
|
||||
def processTraits():
|
||||
print('processing traits')
|
||||
data = _readData('phobos', 'traits')
|
||||
|
||||
def convertSection(sectionData):
|
||||
sectionLines = []
|
||||
headerText = '<b>{}</b>'.format(sectionData['header'])
|
||||
sectionLines.append(headerText)
|
||||
for bonusData in sectionData['bonuses']:
|
||||
prefix = '{} '.format(bonusData['number']) if 'number' in bonusData else ''
|
||||
bonusText = '{}{}'.format(prefix, bonusData['text'].replace('\u00B7', '\u2022 '))
|
||||
sectionLines.append(bonusText)
|
||||
sectionLine = '<br />\n'.join(sectionLines)
|
||||
return sectionLine
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
typeLines = []
|
||||
typeId = row['typeID']
|
||||
traitData = row['traits']
|
||||
for skillData in sorted(traitData.get('skills', ()), key=lambda i: i['header']):
|
||||
typeLines.append(convertSection(skillData))
|
||||
if 'role' in traitData:
|
||||
typeLines.append(convertSection(traitData['role']))
|
||||
if 'misc' in traitData:
|
||||
typeLines.append(convertSection(traitData['misc']))
|
||||
traitLine = '<br />\n<br />\n'.join(typeLines)
|
||||
newRow = {'typeID': typeId, 'traitText': traitLine}
|
||||
newData.append(newRow)
|
||||
|
||||
_addRows(newData, eos.gamedata.Traits)
|
||||
|
||||
def processMetadata():
|
||||
print('processing metadata')
|
||||
data = _readData('phobos', 'metadata')
|
||||
_addRows(data, eos.gamedata.MetaData)
|
||||
|
||||
def processReqSkills(eveTypesData):
|
||||
print('processing requiredskillsfortypes')
|
||||
|
||||
def composeReqSkills(raw):
|
||||
reqSkills = {}
|
||||
for skillTypeID, skillLevels in raw.items():
|
||||
reqSkills[int(skillTypeID)] = skillLevels[0]
|
||||
return reqSkills
|
||||
|
||||
eveTypeIds = set(r['typeID'] for r in eveTypesData)
|
||||
data = _readData('fsd_binary', 'requiredskillsfortypes')
|
||||
reqsByItem = {}
|
||||
itemsByReq = {}
|
||||
for typeID, skillreqData in data.items():
|
||||
typeID = int(typeID)
|
||||
if typeID not in eveTypeIds:
|
||||
continue
|
||||
for skillTypeID, skillLevel in composeReqSkills(skillreqData).items():
|
||||
reqsByItem.setdefault(typeID, {})[skillTypeID] = skillLevel
|
||||
itemsByReq.setdefault(skillTypeID, {})[typeID] = skillLevel
|
||||
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
|
||||
if item.typeID in reqsByItem:
|
||||
item.reqskills = json.dumps(reqsByItem[item.typeID])
|
||||
if item.typeID in itemsByReq:
|
||||
item.requiredfor = json.dumps(itemsByReq[item.typeID])
|
||||
|
||||
def processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData):
|
||||
print('finding item replacements')
|
||||
|
||||
def compareAttrs(attrs1, attrs2):
|
||||
# Consider items as different if they have no attrs
|
||||
if len(attrs1) == 0 and len(attrs2) == 0:
|
||||
return False
|
||||
if set(attrs1) != set(attrs2):
|
||||
return False
|
||||
if all(attrs1[aid] == attrs2[aid] for aid in attrs1):
|
||||
return True
|
||||
return False
|
||||
|
||||
skillReqAttribs = {
|
||||
182: 277,
|
||||
183: 278,
|
||||
184: 279,
|
||||
1285: 1286,
|
||||
1289: 1287,
|
||||
1290: 1288}
|
||||
skillReqAttribsFlat = set(skillReqAttribs.keys()).union(skillReqAttribs.values())
|
||||
# Get data on type groups
|
||||
# Format: {type ID: group ID}
|
||||
typesGroups = {}
|
||||
for row in eveTypesData:
|
||||
typesGroups[row['typeID']] = row['groupID']
|
||||
# Get data on item effects
|
||||
# Format: {type ID: set(effect, IDs)}
|
||||
typesEffects = {}
|
||||
for row in dogmaTypeEffectsData:
|
||||
typesEffects.setdefault(row['typeID'], set()).add(row['effectID'])
|
||||
# Get data on type attributes
|
||||
# Format: {type ID: {attribute ID: attribute value}}
|
||||
typesNormalAttribs = {}
|
||||
typesSkillAttribs = {}
|
||||
for row in dogmaTypeAttributesData:
|
||||
attributeID = row['attributeID']
|
||||
if attributeID in skillReqAttribsFlat:
|
||||
typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
|
||||
typeSkillAttribs[row['attributeID']] = row['value']
|
||||
# Ignore these attributes for comparison purposes
|
||||
elif attributeID in (
|
||||
# We do not need mass as it affects final ship stats only when carried by ship itself
|
||||
# (and we're not going to replace ships), but it's wildly inconsistent for other items,
|
||||
# which otherwise would be the same
|
||||
4, # mass
|
||||
124, # mainColor
|
||||
162, # radius
|
||||
422, # techLevel
|
||||
633, # metaLevel
|
||||
1692, # metaGroupID
|
||||
1768 # typeColorScheme
|
||||
):
|
||||
continue
|
||||
else:
|
||||
typeNormalAttribs = typesNormalAttribs.setdefault(row['typeID'], {})
|
||||
typeNormalAttribs[row['attributeID']] = row['value']
|
||||
# Get data on skill requirements
|
||||
# Format: {type ID: {skill type ID: skill level}}
|
||||
typesSkillReqs = {}
|
||||
for typeID, typeAttribs in typesSkillAttribs.items():
|
||||
typeSkillAttribs = typesSkillAttribs.get(typeID, {})
|
||||
if not typeSkillAttribs:
|
||||
continue
|
||||
typeSkillReqs = typesSkillReqs.setdefault(typeID, {})
|
||||
for skillreqTypeAttr, skillreqLevelAttr in skillReqAttribs.items():
|
||||
try:
|
||||
skillType = int(typeSkillAttribs[skillreqTypeAttr])
|
||||
skillLevel = int(typeSkillAttribs[skillreqLevelAttr])
|
||||
except (KeyError, ValueError):
|
||||
continue
|
||||
typeSkillReqs[skillType] = skillLevel
|
||||
# Format: {group ID: category ID}
|
||||
groupCategories = {}
|
||||
for row in eveGroupsData:
|
||||
groupCategories[row['groupID']] = row['categoryID']
|
||||
# As EVE affects various types mostly depending on their group or skill requirements,
|
||||
# we're going to group various types up this way
|
||||
# Format: {(group ID, frozenset(skillreq, type, IDs), frozenset(type, effect, IDs): [type ID, {attribute ID: attribute value}]}
|
||||
groupedData = {}
|
||||
for row in eveTypesData:
|
||||
typeID = row['typeID']
|
||||
# Ignore items outside of categories we need
|
||||
if groupCategories[typesGroups[typeID]] not in (
|
||||
6, # Ship
|
||||
7, # Module
|
||||
8, # Charge
|
||||
18, # Drone
|
||||
20, # Implant
|
||||
22, # Deployable
|
||||
23, # Starbase
|
||||
32, # Subsystem
|
||||
35, # Decryptors
|
||||
65, # Structure
|
||||
66, # Structure Module
|
||||
87, # Fighter
|
||||
):
|
||||
continue
|
||||
typeAttribs = typesNormalAttribs.get(typeID, {})
|
||||
# Ignore items w/o attributes
|
||||
if not typeAttribs:
|
||||
continue
|
||||
# We need only skill types, not levels for keys
|
||||
typeSkillreqs = frozenset(typesSkillReqs.get(typeID, {}))
|
||||
typeGroup = typesGroups[typeID]
|
||||
typeEffects = frozenset(typesEffects.get(typeID, ()))
|
||||
groupData = groupedData.setdefault((typeGroup, typeSkillreqs, typeEffects), [])
|
||||
groupData.append((typeID, typeAttribs))
|
||||
# Format: {type ID: set(type IDs)}
|
||||
replacements = {}
|
||||
# Now, go through composed groups and for every item within it
|
||||
# find items which are the same
|
||||
for groupData in groupedData.values():
|
||||
for type1, type2 in itertools.combinations(groupData, 2):
|
||||
if compareAttrs(type1[1], type2[1]):
|
||||
replacements.setdefault(type1[0], set()).add(type2[0])
|
||||
replacements.setdefault(type2[0], set()).add(type1[0])
|
||||
# Update DB session with data we generated
|
||||
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
|
||||
itemReplacements = replacements.get(item.typeID)
|
||||
if itemReplacements is not None:
|
||||
item.replacements = ','.join('{}'.format(tid) for tid in sorted(itemReplacements))
|
||||
|
||||
eveTypesData = processEveTypes()
|
||||
eveGroupsData = processEveGroups()
|
||||
processEveCategories()
|
||||
processDogmaAttributes()
|
||||
dogmaTypeAttributesData = processDogmaTypeAttributes(eveTypesData)
|
||||
processDynamicItemAttributes()
|
||||
processDogmaEffects()
|
||||
dogmaTypeEffectsData = processDogmaTypeEffects(eveTypesData)
|
||||
processDogmaUnits()
|
||||
processMarketGroups()
|
||||
processMetaGroups()
|
||||
processCloneGrades()
|
||||
processTraits()
|
||||
processMetadata()
|
||||
|
||||
eos.db.gamedata_session.flush()
|
||||
processReqSkills(eveTypesData)
|
||||
processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData)
|
||||
|
||||
# Add schema version to prevent further updates
|
||||
metadata_schema_version = eos.gamedata.MetaData()
|
||||
metadata_schema_version.field_name = 'schema_version'
|
||||
metadata_schema_version.field_value = GAMEDATA_SCHEMA_VERSION
|
||||
eos.db.gamedata_session.add(metadata_schema_version)
|
||||
|
||||
eos.db.gamedata_session.flush()
|
||||
|
||||
# CCP still has 5 subsystems assigned to T3Cs, even though only 4 are available / usable. They probably have some
|
||||
# old legacy requirement or assumption that makes it difficult for them to change this value in the data. But for
|
||||
# pyfa, we can do it here as a post-processing step
|
||||
for attr in eos.db.gamedata_session.query(eos.gamedata.Attribute).filter(eos.gamedata.Attribute.ID == 1367).all():
|
||||
attr.value = 4.0
|
||||
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(eos.gamedata.Item.name.like('%abyssal%')).all():
|
||||
item.published = False
|
||||
|
||||
for x in [
|
||||
30 # Apparel
|
||||
]:
|
||||
cat = eos.db.gamedata_session.query(eos.gamedata.Category).filter(eos.gamedata.Category.ID == x).first()
|
||||
print ('Removing Category: {}'.format(cat.name))
|
||||
eos.db.gamedata_session.delete(cat)
|
||||
|
||||
eos.db.gamedata_session.commit()
|
||||
eos.db.gamedata_engine.execute('VACUUM')
|
||||
|
||||
print('done')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
update_db()
|
||||
@@ -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', '.'),
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
38
eos/calc.py
38
eos/calc.py
@@ -18,6 +18,38 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
|
||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||
# as multipliers arrive in different form) in here to not make actual attribute
|
||||
# calculations slower than they already are due to extra function calls
|
||||
def calculateMultiplier(multipliers):
|
||||
"""
|
||||
multipliers: dictionary in format:
|
||||
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
|
||||
"""
|
||||
val = 1
|
||||
for penalizedMultipliers in multipliers.values():
|
||||
# A quick explanation of how this works:
|
||||
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
||||
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
||||
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
||||
# 2: The most significant bonuses take the smallest penalty,
|
||||
# This means we'll have to sort
|
||||
abssort = lambda _val: -abs(_val - 1)
|
||||
l1.sort(key=abssort)
|
||||
l2.sort(key=abssort)
|
||||
# 3: The first module doesn't get penalized at all
|
||||
# Any module after the first takes penalties according to:
|
||||
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
||||
for l in (l1, l2):
|
||||
for i in range(len(l)):
|
||||
bonus = l[i]
|
||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||
return val
|
||||
|
||||
|
||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
if distance is None:
|
||||
@@ -31,3 +63,9 @@ def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedR
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
def calculateLockTime(srcScanRes, tgtSigRadius):
|
||||
if not srcScanRes or not tgtSigRadius:
|
||||
return None
|
||||
return min(40000 / srcScanRes / math.asinh(tgtSigRadius) ** 2, 30 * 60)
|
||||
|
||||
@@ -56,6 +56,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)
|
||||
@@ -85,7 +87,7 @@ pyfalog.debug('Importing gamedata DB scheme')
|
||||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
|
||||
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')
|
||||
|
||||
@@ -44,7 +44,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
|
||||
|
||||
|
||||
@@ -92,7 +92,8 @@ def getItem(lookfor, eager=None):
|
||||
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()
|
||||
itemNameMap[lookfor] = item.ID
|
||||
if item is not None:
|
||||
itemNameMap[lookfor] = item.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return item
|
||||
@@ -195,7 +196,8 @@ def getGroup(lookfor, eager=None):
|
||||
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()
|
||||
groupNameMap[lookfor] = group.ID
|
||||
if group is not None:
|
||||
groupNameMap[lookfor] = group.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return group
|
||||
@@ -224,7 +226,8 @@ def getCategory(lookfor, eager=None):
|
||||
# Category names are unique, so we can use first() instead of one()
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
Category.name == lookfor).first()
|
||||
categoryNameMap[lookfor] = category.ID
|
||||
if category is not None:
|
||||
categoryNameMap[lookfor] = category.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return category
|
||||
@@ -253,7 +256,8 @@ def getMetaGroup(lookfor, eager=None):
|
||||
# MetaGroup names are unique, so we can use first() instead of one()
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.name == lookfor).first()
|
||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||
if metaGroup is not None:
|
||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return metaGroup
|
||||
@@ -420,25 +424,3 @@ def getDynamicItem(itemID, eager=None):
|
||||
except exc.NoResultFound:
|
||||
result = 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
|
||||
|
||||
@@ -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
|
||||
|
||||
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))
|
||||
@@ -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
|
||||
@@ -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})
|
||||
|
||||
453
eos/effects.py
453
eos/effects.py
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from logbook import Logger
|
||||
@@ -183,6 +184,8 @@ class Effect(EqBase):
|
||||
self.__activeByDefault = True
|
||||
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"
|
||||
@@ -199,6 +202,8 @@ class Effect(EqBase):
|
||||
|
||||
try:
|
||||
return self.__effectDef.get(key, None)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return getattr(self.__effectDef, key, None)
|
||||
|
||||
@@ -314,50 +319,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 = {
|
||||
@@ -373,7 +354,8 @@ class Item(EqBase):
|
||||
500016: "sisters",
|
||||
500018: "mordu",
|
||||
500019: "sansha",
|
||||
500020: "serpentis"
|
||||
500020: "serpentis",
|
||||
500026: "triglavian"
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -399,6 +381,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",
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import collections
|
||||
|
||||
from collections.abc import MutableMapping
|
||||
from copy import copy
|
||||
from math import exp
|
||||
|
||||
@@ -96,7 +97,7 @@ class ChargeAttrShortcut:
|
||||
return return_value or default
|
||||
|
||||
|
||||
class ModifiedAttributeDict(collections.MutableMapping):
|
||||
class ModifiedAttributeDict(MutableMapping):
|
||||
overrides_enabled = False
|
||||
|
||||
class CalculationPlaceholder:
|
||||
|
||||
@@ -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')):
|
||||
@@ -78,6 +231,15 @@ class DamagePattern:
|
||||
"exp" : "explosive"
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def oneType(cls, damageType, amount=100):
|
||||
pattern = DamagePattern()
|
||||
pattern.update(amount if damageType == "em" else 0,
|
||||
amount if damageType == "thermal" else 0,
|
||||
amount if damageType == "kinetic" else 0,
|
||||
amount if damageType == "explosive" else 0)
|
||||
return pattern
|
||||
|
||||
@classmethod
|
||||
def importPatterns(cls, text):
|
||||
lines = re.split('[\n\r]+', text)
|
||||
@@ -89,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:
|
||||
@@ -98,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
|
||||
@@ -112,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
|
||||
|
||||
@@ -122,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)
|
||||
|
||||
@@ -138,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -21,21 +21,24 @@ import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import asinh, log, sqrt
|
||||
from math import log, sqrt
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos import capSim
|
||||
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.stats import DmgTypes, RRTypes
|
||||
|
||||
|
||||
@@ -162,14 +165,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
|
||||
@@ -177,11 +195,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
|
||||
|
||||
@@ -1529,9 +1561,7 @@ class Fit:
|
||||
def calculateLockTime(self, radius):
|
||||
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
||||
if scanRes is not None and scanRes > 0:
|
||||
# Yes, this function returns time in seconds, not miliseconds.
|
||||
# 40,000 is indeed the correct constant here.
|
||||
return min(40000 / scanRes / asinh(radius) ** 2, 30 * 60)
|
||||
return calculateLockTime(srcScanRes=scanRes, tgtSigRadius=radius)
|
||||
else:
|
||||
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
|
||||
|
||||
@@ -1626,6 +1656,22 @@ class Fit:
|
||||
if ability.active:
|
||||
yield fighter, ability
|
||||
|
||||
def getDampMultScanRes(self):
|
||||
damps = []
|
||||
for mod in self.activeModulesIter():
|
||||
for effectName in ('remoteSensorDampFalloff', 'structureModuleEffectRemoteSensorDampener'):
|
||||
if effectName in mod.item.effects:
|
||||
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
|
||||
if 'doomsdayAOEDamp' in mod.item.effects:
|
||||
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
|
||||
for drone in self.activeDronesIter():
|
||||
if 'remoteSensorDampEntity' in drone.item.effects:
|
||||
damps.extend(drone.amountActive * ((drone.getModifiedItemAttr('scanResolutionBonus'), 'default'),))
|
||||
mults = {}
|
||||
for strength, stackingGroup in damps:
|
||||
mults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
return calculateMultiplier(mults)
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
fitCopy = Fit()
|
||||
# Character and owner are not copied
|
||||
|
||||
@@ -318,36 +318,68 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"energyDestabilizationRange", "empFieldRange",
|
||||
"ecmBurstRange", "warpScrambleRange", "cargoScanRange",
|
||||
"shipScanRange", "surveyScanRange")
|
||||
maxRange = None
|
||||
for attr in attrs:
|
||||
maxRange = self.getModifiedItemAttr(attr, None)
|
||||
if maxRange is not None:
|
||||
return maxRange
|
||||
if self.charge is not None:
|
||||
try:
|
||||
chargeName = self.charge.group.name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if chargeName in ("Scanner Probe", "Survey Probe"):
|
||||
return None
|
||||
break
|
||||
if maxRange is not None:
|
||||
if 'burst projector' in self.item.name.lower():
|
||||
maxRange -= self.owner.ship.getModifiedItemAttr("radius")
|
||||
return maxRange
|
||||
missileMaxRangeData = self.missileMaxRangeData
|
||||
if missileMaxRangeData is None:
|
||||
return None
|
||||
lowerRange, higherRange, higherChance = missileMaxRangeData
|
||||
maxRange = lowerRange * (1 - higherChance) + higherRange * higherChance
|
||||
return maxRange
|
||||
|
||||
@property
|
||||
def missileMaxRangeData(self):
|
||||
if self.charge is None:
|
||||
return None
|
||||
try:
|
||||
chargeName = self.charge.group.name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if chargeName in ("Scanner Probe", "Survey Probe"):
|
||||
return None
|
||||
|
||||
def calculateRange(maxVelocity, mass, agility, flightTime):
|
||||
# Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
|
||||
# D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
|
||||
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
|
||||
flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
|
||||
mass = self.getModifiedChargeAttr("mass")
|
||||
agility = self.getModifiedChargeAttr("agility")
|
||||
if maxVelocity and (flightTime or mass or agility):
|
||||
accelTime = min(flightTime, mass * agility / 1000000)
|
||||
# Average distance done during acceleration
|
||||
duringAcceleration = maxVelocity / 2 * accelTime
|
||||
# Distance done after being at full speed
|
||||
fullSpeed = maxVelocity * (flightTime - accelTime)
|
||||
maxRange = duringAcceleration + fullSpeed
|
||||
if 'fofMissileLaunching' in self.charge.effects:
|
||||
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
|
||||
if rangeLimit:
|
||||
maxRange = min(maxRange, rangeLimit)
|
||||
return maxRange
|
||||
accelTime = min(flightTime, mass * agility / 1000000)
|
||||
# Average distance done during acceleration
|
||||
duringAcceleration = maxVelocity / 2 * accelTime
|
||||
# Distance done after being at full speed
|
||||
fullSpeed = maxVelocity * (flightTime - accelTime)
|
||||
maxRange = duringAcceleration + fullSpeed
|
||||
return maxRange
|
||||
|
||||
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
|
||||
if not maxVelocity:
|
||||
return None
|
||||
shipRadius = self.owner.ship.getModifiedItemAttr("radius")
|
||||
# Flight time has bonus based on ship radius, see https://github.com/pyfa-org/Pyfa/issues/2083
|
||||
flightTime = floatUnerr(self.getModifiedChargeAttr("explosionDelay") / 1000 + shipRadius / maxVelocity)
|
||||
mass = self.getModifiedChargeAttr("mass")
|
||||
agility = self.getModifiedChargeAttr("agility")
|
||||
lowerTime = math.floor(flightTime)
|
||||
higherTime = math.ceil(flightTime)
|
||||
lowerRange = calculateRange(maxVelocity, mass, agility, lowerTime)
|
||||
higherRange = calculateRange(maxVelocity, mass, agility, higherTime)
|
||||
# Fof range limit is supposedly calculated based on overview (surface-to-surface) range
|
||||
if 'fofMissileLaunching' in self.charge.effects:
|
||||
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
|
||||
if rangeLimit:
|
||||
lowerRange = min(lowerRange, rangeLimit)
|
||||
higherRange = min(higherRange, rangeLimit)
|
||||
# Make range center-to-surface, as missiles spawn in the center of the ship
|
||||
lowerRange = max(0, lowerRange - shipRadius)
|
||||
higherRange = max(0, higherRange - shipRadius)
|
||||
higherChance = flightTime - lowerTime
|
||||
return lowerRange, higherRange, higherChance
|
||||
|
||||
@property
|
||||
def falloff(self):
|
||||
|
||||
@@ -73,6 +73,8 @@ 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
|
||||
|
||||
|
||||
@@ -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.287, 0.610, 0.487, 0.401)),
|
||||
(-55, ('[NPC][Invasion]Retaliating Gallente Entities', 0.383, 0.414, 0.578, 0.513)),
|
||||
(-56, ('[NPC][Invasion]Retaliating Minmatar Entities', 0.620, 0.422, 0.355, 0.399)),
|
||||
(-57, ('[NPC][Abyssal][Dark Matter All Tiers]Drones', 0.439, 0.522, 0.529, 0.435)),
|
||||
(-58, ('[NPC][Abyssal][Dark Matter All Tiers]Overmind', 0.626, 0.576, 0.612, 0.624)),
|
||||
(-59, ('[NPC][Abyssal][Dark Matter All Tiers]Seeker', 0.082, 0.082, 0.082, 0.082)),
|
||||
(-60, ('[NPC][Abyssal][Dark Matter All Tiers]Triglavian', 0.477, 0.401, 0.449, 0.37)),
|
||||
(-61, ('[NPC][Abyssal][Dark Matter All Tiers]Drifter', 0.403, 0.403, 0.403, 0.403)),
|
||||
(-62, ('[NPC][Abyssal][Dark Matter All Tiers]Sleeper', 0.435, 0.435, 0.435, 0.435)),
|
||||
(-63, ('[NPC][Abyssal][Dark Matter All Tiers]All', 0.507, 0.477, 0.502, 0.493)),
|
||||
(-64, ('[NPC][Abyssal][Electrical T1/T2]Drones', 0.323, 0.522, 0.529, 0.435)),
|
||||
(-65, ('[NPC][Abyssal][Electrical T1/T2]Overmind', 0.521, 0.576, 0.612, 0.624)),
|
||||
(-66, ('[NPC][Abyssal][Electrical T1/T2]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||
(-67, ('[NPC][Abyssal][Electrical T1/T2]Triglavian', 0.333, 0.401, 0.449, 0.37)),
|
||||
(-68, ('[NPC][Abyssal][Electrical T1/T2]Drifter', 0.267, 0.403, 0.403, 0.403)),
|
||||
(-69, ('[NPC][Abyssal][Electrical T1/T2]Sleeper', 0.329, 0.435, 0.435, 0.435)),
|
||||
(-70, ('[NPC][Abyssal][Electrical T1/T2]All', 0.385, 0.477, 0.502, 0.493)),
|
||||
(-71, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drones', 0.255, 0.522, 0.529, 0.435)),
|
||||
(-72, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Overmind', 0.457, 0.576, 0.612, 0.624)),
|
||||
(-73, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||
(-74, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Triglavian', 0.241, 0.401, 0.449, 0.37)),
|
||||
(-75, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drifter', 0.184, 0.403, 0.403, 0.403)),
|
||||
(-76, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Sleeper', 0.268, 0.435, 0.435, 0.435)),
|
||||
(-77, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]All', 0.313, 0.477, 0.502, 0.493)),
|
||||
(-78, ('[NPC][Abyssal][Electrical T4/T5]Drones', 0.193, 0.522, 0.529, 0.435)),
|
||||
(-79, ('[NPC][Abyssal][Electrical T4/T5]Overmind', 0.398, 0.576, 0.612, 0.624)),
|
||||
(-80, ('[NPC][Abyssal][Electrical T4/T5]Seeker', 0, 0.082, 0.082, 0.082)),
|
||||
(-81, ('[NPC][Abyssal][Electrical T4/T5]Triglavian', 0.183, 0.401, 0.449, 0.37)),
|
||||
(-82, ('[NPC][Abyssal][Electrical T4/T5]Drifter', 0.107, 0.403, 0.403, 0.403)),
|
||||
(-83, ('[NPC][Abyssal][Electrical T4/T5]Sleeper', 0.215, 0.435, 0.435, 0.435)),
|
||||
(-84, ('[NPC][Abyssal][Electrical T4/T5]All', 0.25, 0.477, 0.502, 0.493)),
|
||||
(-85, ('[NPC][Abyssal][Firestorm T1/T2]Drones', 0.461, 0.425, 0.541, 0.443)),
|
||||
(-86, ('[NPC][Abyssal][Firestorm T1/T2]Overmind', 0.65, 0.469, 0.625, 0.633)),
|
||||
(-87, ('[NPC][Abyssal][Firestorm T1/T2]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||
(-88, ('[NPC][Abyssal][Firestorm T1/T2]Triglavian', 0.534, 0.266, 0.484, 0.366)),
|
||||
(-89, ('[NPC][Abyssal][Firestorm T1/T2]Drifter', 0.422, 0.282, 0.422, 0.422)),
|
||||
(-90, ('[NPC][Abyssal][Firestorm T1/T2]Sleeper', 0.512, 0.402, 0.512, 0.512)),
|
||||
(-91, ('[NPC][Abyssal][Firestorm T1/T2]All', 0.541, 0.365, 0.524, 0.504)),
|
||||
(-92, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drones', 0.461, 0.36, 0.541, 0.443)),
|
||||
(-93, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Overmind', 0.65, 0.391, 0.625, 0.633)),
|
||||
(-94, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||
(-95, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Triglavian', 0.534, 0.161, 0.484, 0.366)),
|
||||
(-96, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drifter', 0.422, 0.196, 0.422, 0.422)),
|
||||
(-97, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Sleeper', 0.512, 0.337, 0.512, 0.512)),
|
||||
(-98, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]All', 0.541, 0.284, 0.524, 0.504)),
|
||||
(-99, ('[NPC][Abyssal][Firestorm T4/T5]Drones', 0.461, 0.305, 0.541, 0.443)),
|
||||
(-100, ('[NPC][Abyssal][Firestorm T4/T5]Overmind', 0.65, 0.323, 0.625, 0.633)),
|
||||
(-101, ('[NPC][Abyssal][Firestorm T4/T5]Seeker', 0.084, 0, 0.084, 0.084)),
|
||||
(-102, ('[NPC][Abyssal][Firestorm T4/T5]Triglavian', 0.534, 0.082, 0.484, 0.366)),
|
||||
(-103, ('[NPC][Abyssal][Firestorm T4/T5]Drifter', 0.422, 0.114, 0.422, 0.422)),
|
||||
(-104, ('[NPC][Abyssal][Firestorm T4/T5]Sleeper', 0.512, 0.276, 0.512, 0.512)),
|
||||
(-105, ('[NPC][Abyssal][Firestorm T4/T5]All', 0.541, 0.214, 0.524, 0.504)),
|
||||
(-106, ('[NPC][Abyssal][Exotic T1/T2]Drones', 0.439, 0.522, 0.417, 0.435)),
|
||||
(-107, ('[NPC][Abyssal][Exotic T1/T2]Overmind', 0.626, 0.576, 0.496, 0.624)),
|
||||
(-108, ('[NPC][Abyssal][Exotic T1/T2]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||
(-109, ('[NPC][Abyssal][Exotic T1/T2]Triglavian', 0.477, 0.401, 0.284, 0.37)),
|
||||
(-110, ('[NPC][Abyssal][Exotic T1/T2]Drifter', 0.403, 0.403, 0.267, 0.403)),
|
||||
(-111, ('[NPC][Abyssal][Exotic T1/T2]Sleeper', 0.435, 0.435, 0.329, 0.435)),
|
||||
(-112, ('[NPC][Abyssal][Exotic T1/T2]All', 0.507, 0.477, 0.373, 0.493)),
|
||||
(-113, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drones', 0.439, 0.522, 0.351, 0.435)),
|
||||
(-114, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Overmind', 0.626, 0.576, 0.419, 0.624)),
|
||||
(-115, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||
(-116, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Triglavian', 0.477, 0.401, 0.176, 0.37)),
|
||||
(-117, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drifter', 0.403, 0.403, 0.184, 0.403)),
|
||||
(-118, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.268, 0.435)),
|
||||
(-119, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]All', 0.507, 0.477, 0.293, 0.493)),
|
||||
(-120, ('[NPC][Abyssal][Exotic T4/T5]Drones', 0.439, 0.522, 0.293, 0.435)),
|
||||
(-121, ('[NPC][Abyssal][Exotic T4/T5]Overmind', 0.626, 0.576, 0.344, 0.624)),
|
||||
(-122, ('[NPC][Abyssal][Exotic T4/T5]Seeker', 0.082, 0.082, 0, 0.082)),
|
||||
(-123, ('[NPC][Abyssal][Exotic T4/T5]Triglavian', 0.477, 0.401, 0.107, 0.37)),
|
||||
(-124, ('[NPC][Abyssal][Exotic T4/T5]Drifter', 0.403, 0.403, 0.107, 0.403)),
|
||||
(-125, ('[NPC][Abyssal][Exotic T4/T5]Sleeper', 0.435, 0.435, 0.215, 0.435)),
|
||||
(-126, ('[NPC][Abyssal][Exotic T4/T5]All', 0.507, 0.477, 0.223, 0.493)),
|
||||
(-127, ('[NPC][Abyssal][Gamma T1/T2]Drones', 0.449, 0.54, 0.549, 0.336)),
|
||||
(-128, ('[NPC][Abyssal][Gamma T1/T2]Overmind', 0.6, 0.557, 0.601, 0.504)),
|
||||
(-129, ('[NPC][Abyssal][Gamma T1/T2]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||
(-130, ('[NPC][Abyssal][Gamma T1/T2]Triglavian', 0.463, 0.392, 0.447, 0.193)),
|
||||
(-131, ('[NPC][Abyssal][Gamma T1/T2]Drifter', 0.428, 0.428, 0.428, 0.287)),
|
||||
(-132, ('[NPC][Abyssal][Gamma T1/T2]Sleeper', 0.435, 0.435, 0.435, 0.329)),
|
||||
(-133, ('[NPC][Abyssal][Gamma T1/T2]All', 0.493, 0.472, 0.5, 0.362)),
|
||||
(-134, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drones', 0.449, 0.54, 0.549, 0.264)),
|
||||
(-135, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Overmind', 0.6, 0.557, 0.601, 0.428)),
|
||||
(-136, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||
(-137, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Triglavian', 0.463, 0.392, 0.447, 0.071)),
|
||||
(-138, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drifter', 0.428, 0.428, 0.428, 0.2)),
|
||||
(-139, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.435, 0.268)),
|
||||
(-140, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]All', 0.493, 0.472, 0.5, 0.28)),
|
||||
(-141, ('[NPC][Abyssal][Gamma T4/T5]Drones', 0.449, 0.54, 0.549, 0.197)),
|
||||
(-142, ('[NPC][Abyssal][Gamma T4/T5]Overmind', 0.6, 0.557, 0.601, 0.356)),
|
||||
(-143, ('[NPC][Abyssal][Gamma T4/T5]Seeker', 0.085, 0.085, 0.085, 0)),
|
||||
(-144, ('[NPC][Abyssal][Gamma T4/T5]Triglavian', 0.463, 0.392, 0.447, 0.029)),
|
||||
(-145, ('[NPC][Abyssal][Gamma T4/T5]Drifter', 0.428, 0.428, 0.428, 0.117)),
|
||||
(-146, ('[NPC][Abyssal][Gamma T4/T5]Sleeper', 0.435, 0.435, 0.435, 0.215)),
|
||||
(-147, ('[NPC][Abyssal][Gamma T4/T5]All', 0.493, 0.472, 0.5, 0.21))])
|
||||
|
||||
|
||||
class TargetProfile:
|
||||
|
||||
# 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
|
||||
@@ -46,11 +46,11 @@ class DmgTypes:
|
||||
# Round for comparison's sake because often damage profiles are
|
||||
# generated from data which includes float errors
|
||||
return (
|
||||
floatUnerr(self.em) == floatUnerr(other.em) and
|
||||
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
|
||||
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
|
||||
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
|
||||
floatUnerr(self.total) == floatUnerr(other.total))
|
||||
floatUnerr(self.em) == floatUnerr(other.em) and
|
||||
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
|
||||
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
|
||||
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
|
||||
floatUnerr(self.total) == floatUnerr(other.total))
|
||||
|
||||
def __bool__(self):
|
||||
return any((
|
||||
@@ -110,9 +110,19 @@ class DmgTypes:
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
spec = ['em', 'thermal', 'kinetic', 'explosive', 'total']
|
||||
spec = DmgTypes.names()
|
||||
spec.append('total')
|
||||
return makeReprStr(self, spec)
|
||||
|
||||
@staticmethod
|
||||
def names(short=None, postProcessor=None):
|
||||
value = ['em', 'th', 'kin', 'exp'] if short else ['em', 'thermal', 'kinetic', 'explosive']
|
||||
|
||||
if postProcessor:
|
||||
value = [postProcessor(x) for x in value]
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class RRTypes:
|
||||
"""Container for tank data stats."""
|
||||
@@ -136,10 +146,10 @@ class RRTypes:
|
||||
# Round for comparison's sake because often tanking numbers are
|
||||
# generated from data which includes float errors
|
||||
return (
|
||||
floatUnerr(self.shield) == floatUnerr(other.shield) and
|
||||
floatUnerr(self.armor) == floatUnerr(other.armor) and
|
||||
floatUnerr(self.hull) == floatUnerr(other.hull) and
|
||||
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
|
||||
floatUnerr(self.shield) == floatUnerr(other.shield) and
|
||||
floatUnerr(self.armor) == floatUnerr(other.armor) and
|
||||
floatUnerr(self.hull) == floatUnerr(other.hull) and
|
||||
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
|
||||
|
||||
def __bool__(self):
|
||||
return any((self.shield, self.armor, self.hull, self.capacitor))
|
||||
@@ -191,5 +201,17 @@ class RRTypes:
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
spec = ['shield', 'armor', 'hull', 'capacitor']
|
||||
spec = RRTypes.names(False)
|
||||
return makeReprStr(self, spec)
|
||||
|
||||
@staticmethod
|
||||
def names(ehpOnly=True, postProcessor=None):
|
||||
value = ['shield', 'armor', 'hull']
|
||||
|
||||
if not ehpOnly:
|
||||
value.append('capacitor')
|
||||
|
||||
if postProcessor:
|
||||
value = [postProcessor(x) for x in value]
|
||||
|
||||
return value
|
||||
|
||||
@@ -18,40 +18,9 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||
# as multipliers arrive in different form) in here to not make actual attribute
|
||||
# calculations slower than they already are due to extra function calls
|
||||
def calculateMultiplier(multipliers):
|
||||
"""
|
||||
multipliers: dictionary in format:
|
||||
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
|
||||
"""
|
||||
val = 1
|
||||
for penalizedMultipliers in multipliers.values():
|
||||
# A quick explanation of how this works:
|
||||
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
||||
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
||||
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
||||
# 2: The most significant bonuses take the smallest penalty,
|
||||
# This means we'll have to sort
|
||||
abssort = lambda _val: -abs(_val - 1)
|
||||
l1.sort(key=abssort)
|
||||
l2.sort(key=abssort)
|
||||
# 3: The first module doesn't get penalized at all
|
||||
# Any module after the first takes penalties according to:
|
||||
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
||||
for l in (l1, l2):
|
||||
for i in range(len(l)):
|
||||
bonus = l[i]
|
||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||
return val
|
||||
|
||||
|
||||
def checkLockRange(src, distance):
|
||||
if distance is None:
|
||||
return True
|
||||
|
||||
@@ -26,3 +26,5 @@ from . import fitCapacitor
|
||||
from . import fitMobility
|
||||
from . import fitWarpTime
|
||||
from . import fitLockTime
|
||||
# Hidden graphs, available via ctrl-alt-g
|
||||
from . import fitEcmBurstScanresDamps
|
||||
|
||||
@@ -29,6 +29,7 @@ from service.const import GraphCacheCleanupReason
|
||||
class FitGraph(metaclass=ABCMeta):
|
||||
|
||||
# UI stuff
|
||||
hidden = False
|
||||
views = []
|
||||
viewMap = {}
|
||||
|
||||
|
||||
@@ -152,18 +152,24 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
|
||||
|
||||
|
||||
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
|
||||
modRange = mod.maxRange
|
||||
if modRange is None:
|
||||
missileMaxRangeData = mod.missileMaxRangeData
|
||||
if missileMaxRangeData is None:
|
||||
return 0
|
||||
if distance is not None and distance + src.getRadius() > modRange:
|
||||
return 0
|
||||
mult = _calcMissileFactor(
|
||||
# The ranges already consider ship radius
|
||||
lowerRange, higherRange, higherChance = missileMaxRangeData
|
||||
if distance is None or distance <= lowerRange:
|
||||
distanceFactor = 1
|
||||
elif lowerRange < distance <= higherRange:
|
||||
distanceFactor = higherChance
|
||||
else:
|
||||
distanceFactor = 0
|
||||
applicationFactor = _calcMissileFactor(
|
||||
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
|
||||
atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
|
||||
atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
return mult
|
||||
return distanceFactor * applicationFactor
|
||||
|
||||
|
||||
def getSmartbombMult(mod, distance):
|
||||
|
||||
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from .graph import FitEcmBurstScanresDampsGraph
|
||||
|
||||
|
||||
FitEcmBurstScanresDampsGraph.register()
|
||||
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.calc import calculateLockTime
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
ECM_BURST_DURATION = 30
|
||||
DRONE_LOCK_TIME = 2
|
||||
|
||||
|
||||
class TgtScanRes2TgtLockTimeGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
if miscParams['applyDamps']:
|
||||
tgtScanResMult = src.item.getDampMultScanRes()
|
||||
else:
|
||||
tgtScanResMult = 1
|
||||
return {
|
||||
'tgtScanResMult': tgtScanResMult,
|
||||
'sigRadius': src.item.ship.getModifiedItemAttr('signatureRadius')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
scanRes = x
|
||||
time = calculateLockTime(
|
||||
srcScanRes=scanRes * commonData['tgtScanResMult'],
|
||||
tgtSigRadius=commonData['sigRadius'])
|
||||
return time
|
||||
|
||||
|
||||
class TgtScanRes2TgtLockUptimeGetter(TgtScanRes2TgtLockTimeGetter):
|
||||
|
||||
def _calculatePoint(self, *args, **kwargs):
|
||||
# Assuming you ECM burst every 30 seconds, find out how long you
|
||||
# will be locked before you burst another time
|
||||
lockTime = super()._calculatePoint(*args, **kwargs)
|
||||
lockedTime = max(0, ECM_BURST_DURATION - lockTime)
|
||||
return lockedTime
|
||||
|
||||
|
||||
class SrcDmgBaseGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
if miscParams['applyDamps']:
|
||||
tgtScanResMult = src.item.getDampMultScanRes()
|
||||
else:
|
||||
tgtScanResMult = 1
|
||||
return {
|
||||
'tgtScanResMult': tgtScanResMult,
|
||||
'srcSigRadius': src.item.ship.getModifiedItemAttr('signatureRadius'),
|
||||
'srcEhp': sum(src.item.ehp.values()),
|
||||
'srcDpsWeapon': src.item.getWeaponDps().total,
|
||||
'srcDpsDrone': src.item.getDroneDps().total if miscParams['applyDrones'] else 0}
|
||||
|
||||
@staticmethod
|
||||
def _calculateInflictedDamage(srcSigRadius, srcWeaponDps, srcDroneDps, srcEhp, tgtScanRes, tgtDps, uptimeAdjustment, uptimeAmountLimit):
|
||||
lockTime = calculateLockTime(srcScanRes=tgtScanRes, tgtSigRadius=srcSigRadius)
|
||||
lockUptime = max(0, ECM_BURST_DURATION - lockTime - uptimeAdjustment)
|
||||
lockDowntime = ECM_BURST_DURATION - lockUptime
|
||||
inflictedDmg = 0
|
||||
remainingEhp = srcEhp
|
||||
for i in range(int(uptimeAmountLimit)):
|
||||
timeAliveUnderFire = min(lockUptime, remainingEhp / tgtDps)
|
||||
timeAlive = lockDowntime + timeAliveUnderFire
|
||||
remainingEhp -= lockUptime * tgtDps
|
||||
inflictedDmg += timeAlive * srcWeaponDps
|
||||
inflictedDmg += max(0, timeAlive - DRONE_LOCK_TIME - 1) * srcDroneDps
|
||||
if remainingEhp <= 0:
|
||||
break
|
||||
return inflictedDmg
|
||||
|
||||
|
||||
class TgtScanRes2SrcDmgGetter(SrcDmgBaseGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
damage = self._calculateInflictedDamage(
|
||||
srcSigRadius=commonData['srcSigRadius'],
|
||||
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||
srcDroneDps=commonData['srcDpsDrone'],
|
||||
srcEhp=commonData['srcEhp'],
|
||||
tgtScanRes=x * commonData['tgtScanResMult'],
|
||||
tgtDps=miscParams['tgtDps'],
|
||||
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||
return damage
|
||||
|
||||
|
||||
class TgtDps2SrcDmgGetter(SrcDmgBaseGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
damage = self._calculateInflictedDamage(
|
||||
srcSigRadius=commonData['srcSigRadius'],
|
||||
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||
srcDroneDps=commonData['srcDpsDrone'],
|
||||
srcEhp=commonData['srcEhp'],
|
||||
tgtScanRes=miscParams['tgtScanRes'] * commonData['tgtScanResMult'],
|
||||
tgtDps=x,
|
||||
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||
return damage
|
||||
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
"""
|
||||
Disclaimer by kadesh: this graph was made to analyze my ECM burst + damp frig
|
||||
concept. I do not think it is useful for regular player, so it is disabled.
|
||||
Enable by setting config.experimentalFeatures = True.
|
||||
"""
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from graphs.data.base import FitGraph, XDef, YDef, Input, InputCheckbox
|
||||
from .getter import (
|
||||
TgtScanRes2TgtLockTimeGetter, TgtScanRes2TgtLockUptimeGetter,
|
||||
TgtScanRes2SrcDmgGetter, TgtDps2SrcDmgGetter)
|
||||
|
||||
|
||||
class FitEcmBurstScanresDampsGraph(FitGraph):
|
||||
|
||||
# UI stuff
|
||||
hidden = True
|
||||
internalName = 'ecmBurstScanresDamps'
|
||||
name = 'ECM Burst + Scanres Damps'
|
||||
xDefs = [
|
||||
XDef(handle='tgtDps', unit=None, label='Enemy DPS', mainInput=('tgtDps', None)),
|
||||
XDef(handle='tgtScanRes', unit='mm', label='Enemy scanres', mainInput=('tgtScanRes', 'mm'))]
|
||||
yDefs = [
|
||||
YDef(handle='srcDmg', unit=None, label='Damage inflicted'),
|
||||
YDef(handle='tgtLockTime', unit='s', label='Lock time'),
|
||||
YDef(handle='tgtLockUptime', unit='s', label='Lock uptime')]
|
||||
inputs = [
|
||||
Input(handle='tgtScanRes', unit='mm', label='Enemy scanres', iconID=74, defaultValue=700, defaultRange=(100, 1000)),
|
||||
Input(handle='tgtDps', unit=None, label='Enemy DPS', iconID=1432, defaultValue=200, defaultRange=(100, 600)),
|
||||
Input(handle='uptimeAdj', unit='s', label='Uptime adjustment', iconID=1392, defaultValue=1, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))]),
|
||||
Input(handle='uptimeAmtLimit', unit=None, label='Max amount of uptimes', iconID=1397, defaultValue=3, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))])]
|
||||
checkboxes = [
|
||||
InputCheckbox(handle='applyDamps', label='Apply sensor dampeners', defaultValue=True),
|
||||
InputCheckbox(handle='applyDrones', label='Use drones', defaultValue=True, conditions=[(None, ('srcDmg', None))])]
|
||||
srcExtraCols = ('SigRadius', 'Damp ScanRes')
|
||||
|
||||
# Calculation stuff
|
||||
_limiters = {'tgtScanRes': lambda src, tgt: (1, math.inf)}
|
||||
_getters = {
|
||||
('tgtScanRes', 'tgtLockTime'): TgtScanRes2TgtLockTimeGetter,
|
||||
('tgtScanRes', 'tgtLockUptime'): TgtScanRes2TgtLockUptimeGetter,
|
||||
('tgtScanRes', 'srcDmg'): TgtScanRes2SrcDmgGetter,
|
||||
('tgtDps', 'srcDmg'): TgtDps2SrcDmgGetter}
|
||||
@@ -20,8 +20,8 @@
|
||||
|
||||
import math
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from graphs.calc import calculateMultiplier, checkLockRange, checkDroneControlRange
|
||||
from eos.calc import calculateMultiplier, calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
import math
|
||||
|
||||
from graphs.data.base import FitGraph, Input, XDef, YDef
|
||||
from graphs.data.base import FitGraph, XDef, YDef, Input
|
||||
from .getter import TgtSigRadius2LockTimeGetter
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,7 +43,7 @@ REDRAW_DELAY = 500
|
||||
|
||||
class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, includeHidden=False):
|
||||
if not canvasPanel.graphFrame_enabled:
|
||||
pyfalog.warning('Matplotlib is not enabled. Skipping initialization.')
|
||||
return
|
||||
@@ -74,6 +74,8 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
# Setup - graph selector
|
||||
for view in FitGraph.views:
|
||||
if view.hidden and not includeHidden:
|
||||
continue
|
||||
self.graphSelection.Append(view.name, view())
|
||||
self.graphSelection.SetSelection(0)
|
||||
self.ctrlPanel.updateControls(layout=False)
|
||||
@@ -101,9 +103,9 @@ class GraphFrame(AuxiliaryFrame):
|
||||
self.draw()
|
||||
|
||||
@classmethod
|
||||
def openOne(cls, parent):
|
||||
def openOne(cls, parent, *args, **kwargs):
|
||||
if canvasPanel.graphFrame_enabled:
|
||||
super().openOne(parent)
|
||||
super().openOne(parent, *args, **kwargs)
|
||||
|
||||
def UpdateWindowSize(self):
|
||||
curW, curH = self.GetSize()
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.calc import calculateMultiplier
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from service.const import TargetResistMode
|
||||
from .calc import calculateMultiplier
|
||||
|
||||
|
||||
class BaseWrapper:
|
||||
@@ -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=()):
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
|
||||
import config
|
||||
|
||||
versionString = "{0}".format(config.version)
|
||||
try:
|
||||
versionString = "{0}".format(config.getVersion())
|
||||
except NameError:
|
||||
# is caught in case we run test and there are no config values initialized
|
||||
versionString = "0.0"
|
||||
|
||||
licenses = (
|
||||
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
||||
"All EVE-Online related materials are property of CCP hf.",
|
||||
|
||||
@@ -53,10 +53,10 @@ class AuxiliaryFrame(wx.Frame):
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
@classmethod
|
||||
def openOne(cls, parent):
|
||||
def openOne(cls, parent, *args, **kwargs):
|
||||
"""If window is open and alive - raise it, open otherwise"""
|
||||
if not cls._instance:
|
||||
frame = cls(parent)
|
||||
frame = cls(parent, *args, **kwargs)
|
||||
cls._instance = frame
|
||||
frame.Show()
|
||||
else:
|
||||
|
||||
@@ -33,12 +33,17 @@ pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class BitmapLoader:
|
||||
try:
|
||||
archive = zipfile.ZipFile(config.imgsZIP, 'r')
|
||||
pyfalog.info("Using zipped image files.")
|
||||
except (IOError, TypeError):
|
||||
# Can be None if we're running from tests
|
||||
if config.imgsZIP is None:
|
||||
pyfalog.info("Using local image files.")
|
||||
archive = None
|
||||
else:
|
||||
try:
|
||||
archive = zipfile.ZipFile(config.imgsZIP, 'r')
|
||||
pyfalog.info("Using zipped image files.")
|
||||
except (IOError, TypeError):
|
||||
pyfalog.info("Using local image files.")
|
||||
archive = None
|
||||
|
||||
cached_bitmaps = OrderedDict()
|
||||
dont_use_cached_bitmaps = False
|
||||
@@ -84,7 +89,7 @@ class BitmapLoader:
|
||||
@classmethod
|
||||
def loadBitmap(cls, name, location):
|
||||
if cls.scaling_factor is None:
|
||||
cls.scaling_factor = int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
|
||||
cls.scaling_factor = 1 if 'wxGTK' in wx.PlatformInfo else int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
|
||||
scale = cls.scaling_factor
|
||||
|
||||
filename, img = cls.loadScaledBitmap(name, location, scale)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -36,7 +36,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
|
||||
|
||||
@@ -121,18 +121,18 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
|
||||
# Expressions for matching when detecting effects we're looking for
|
||||
if incursions:
|
||||
validgroups = ("Incursion ship attributes effects",
|
||||
"Invasion Effects")
|
||||
validgroups = ("Sansha Incursion",
|
||||
"Triglavian Invasion")
|
||||
else:
|
||||
validgroups = ("Black Hole Effect Beacon",
|
||||
"Cataclysmic Variable Effect Beacon",
|
||||
"Magnetar Effect Beacon",
|
||||
"Pulsar Effect Beacon",
|
||||
"Red Giant Beacon",
|
||||
"Wolf Rayet Effect Beacon")
|
||||
validgroups = ("Black Hole",
|
||||
"Cataclysmic Variable",
|
||||
"Magnetar",
|
||||
"Pulsar",
|
||||
"Red Giant",
|
||||
"Wolf Rayet")
|
||||
|
||||
# Stuff we don't want to see in names
|
||||
garbages = ("Effects?", "Beacon", "ship attributes effects")
|
||||
garbages = ("System Effects", "Effects")
|
||||
|
||||
# Get group with all the system-wide beacons
|
||||
grp = sMkt.getGroup("Effect Beacon")
|
||||
@@ -142,7 +142,7 @@ class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
# Check if it belongs to any valid group
|
||||
for group in validgroups:
|
||||
# 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:
|
||||
|
||||
@@ -5,7 +5,7 @@ from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.implantSet import ImplantSets as s_ImplantSets
|
||||
|
||||
|
||||
class AddImplantSet(ContextMenuUnconditional):
|
||||
class ImplantSetApply(ContextMenuUnconditional):
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
|
||||
@@ -14,10 +14,11 @@ class AddImplantSet(ContextMenuUnconditional):
|
||||
|
||||
if len(implantSets) == 0:
|
||||
return False
|
||||
|
||||
return srcContext in ("implantItemMisc", "implantEditor")
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return "Add Implant Set"
|
||||
def getText(self, callingWindow, context):
|
||||
return "Apply Implant Set"
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
@@ -49,4 +50,4 @@ class AddImplantSet(ContextMenuUnconditional):
|
||||
self.callingWindow.addImplantSet(impSet)
|
||||
|
||||
|
||||
AddImplantSet.register()
|
||||
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
|
||||
|
||||
|
||||
@@ -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.profileEventMap.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.profileEventMap[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.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
|
||||
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,7 +123,7 @@ 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:
|
||||
@@ -127,7 +132,7 @@ class AttributeSlider(wx.Panel):
|
||||
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
@@ -171,6 +172,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 +191,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 +224,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.
|
||||
|
||||
@@ -49,8 +49,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):
|
||||
@@ -180,7 +178,7 @@ class ItemView(Display):
|
||||
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
|
||||
@@ -214,7 +212,7 @@ class ItemView(Display):
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = self.metaMap.get(metagrpid)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
@@ -259,18 +257,6 @@ class ItemView(Display):
|
||||
|
||||
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()
|
||||
|
||||
@@ -63,6 +63,8 @@ class MarketTree(wx.TreeCtrl):
|
||||
iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp))
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import wx
|
||||
|
||||
import config
|
||||
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
|
||||
from eos.db.saveddata.queries import clearPrices, clearDamagePatterns, clearTargetProfiles
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.preferenceView import PreferenceView
|
||||
@@ -75,10 +74,6 @@ class PFGeneralPref(PreferenceView):
|
||||
btnSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
btnSizer.AddStretchSpacer()
|
||||
|
||||
self.btnImportDefaults = wx.Button(panel, wx.ID_ANY, "Reimport Database Defaults", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.btnImportDefaults, 0, wx.ALL, 5)
|
||||
self.btnImportDefaults.Bind(wx.EVT_BUTTON, self.loadDatabaseDefaults)
|
||||
|
||||
self.btnDeleteDamagePatterns = wx.Button(panel, wx.ID_ANY, "Delete All Damage Pattern Profiles", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.btnDeleteDamagePatterns, 0, wx.ALL, 5)
|
||||
self.btnDeleteDamagePatterns.Bind(wx.EVT_BUTTON, self.DeleteDamagePatterns)
|
||||
@@ -97,14 +92,6 @@ class PFGeneralPref(PreferenceView):
|
||||
panel.SetSizer(mainSizer)
|
||||
panel.Layout()
|
||||
|
||||
def loadDatabaseDefaults(self, event):
|
||||
# Import values that must exist otherwise Pyfa breaks
|
||||
DefaultDatabaseValues.importRequiredDefaults()
|
||||
# Import default values for damage profiles
|
||||
DefaultDatabaseValues.importDamageProfileDefaults()
|
||||
# Import default values for target resist profiles
|
||||
DefaultDatabaseValues.importTargetProfileDefaults()
|
||||
|
||||
def DeleteDamagePatterns(self, event):
|
||||
question = "This is a destructive action that will delete all damage pattern profiles.\nAre you sure you want to do this?"
|
||||
if wxHelpers.YesNoDialog(question, "Confirm"):
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import wx
|
||||
from gui.statsView import StatsView
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
|
||||
|
||||
class CapacitorViewFull(StatsView):
|
||||
@@ -133,14 +133,20 @@ class CapacitorViewFull(StatsView):
|
||||
label.SetLabel('{}{}'.format(formatAmount(value, prec, lowest, highest, forceSign=forceSign), unit))
|
||||
label.SetToolTip(wx.ToolTip("%.1f" % value))
|
||||
|
||||
if labelName == 'label%sCapacitorDelta':
|
||||
label_tooltip = 'Capacitor delta:\n+{} GJ/s\n-{} GJ/s'.format(
|
||||
formatAmount(cap_recharge, 3, 0, 3),
|
||||
formatAmount(cap_use, 3, 0, 3))
|
||||
label.SetToolTip(wx.ToolTip(label_tooltip))
|
||||
if labelName == 'label%sCapacitorDelta' and (cap_recharge or cap_use):
|
||||
lines = []
|
||||
lines.append('Capacitor delta:')
|
||||
lines.append(' +{} GJ/s'.format(formatAmount(cap_recharge, 3, 0, 3)))
|
||||
lines.append(' -{} GJ/s'.format(formatAmount(cap_use, 3, 0, 3)))
|
||||
delta = round(cap_recharge - cap_use, 3)
|
||||
if delta > 0 and 0 < round(neut_res, 4) < 1:
|
||||
lines.append('')
|
||||
lines.append('Effective excessive gain:')
|
||||
lines.append(' +{} GJ/s'.format(formatAmount(delta / neut_res, 3, 0, 3)))
|
||||
label.SetToolTip(wx.ToolTip('\n'.join(lines)))
|
||||
if labelName == 'label%sCapacitorResist':
|
||||
texts = ['Neutralizer resistance']
|
||||
if cap_amount > 0 and neut_res < 1:
|
||||
if cap_amount > 0 and 0 < round(neut_res, 4) < 1:
|
||||
texts.append('Effective capacity: {} GJ'.format(formatAmount(cap_amount / neut_res, 3, 0, 9)))
|
||||
label.SetToolTip(wx.ToolTip('\n'.join(texts)))
|
||||
|
||||
|
||||
@@ -151,52 +151,71 @@ class FirepowerViewFull(StatsView):
|
||||
else:
|
||||
self.stEff.Hide()
|
||||
|
||||
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest):
|
||||
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||
def hasSpoolUp(preSpool, fullSpool):
|
||||
if preSpool is None or fullSpool is None:
|
||||
return False
|
||||
return roundToPrec(preSpool.total, prec) != roundToPrec(fullSpool.total, prec)
|
||||
|
||||
def dpsToolTip(normal, preSpool, fullSpool, prec, lowest, highest):
|
||||
if normal is None or preSpool is None or fullSpool is None:
|
||||
return ""
|
||||
else:
|
||||
return "Spool up: {}-{}".format(
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
hasSpool = hasSpoolUp(preSpool, fullSpool)
|
||||
lines = []
|
||||
if hasSpool:
|
||||
lines.append("Spool up: {}-{}".format(
|
||||
formatAmount(preSpool.total, prec, lowest, highest),
|
||||
formatAmount(fullSpool.total, prec, lowest, highest)))
|
||||
if getattr(normal, 'total', None):
|
||||
if hasSpool:
|
||||
lines.append("")
|
||||
lines.append("Current: {}".format(formatAmount(normal.total, prec, lowest, highest)))
|
||||
for dmgType in normal.names():
|
||||
val = getattr(normal, dmgType, None)
|
||||
if val:
|
||||
lines.append("{}{}: {}%".format(
|
||||
" " if hasSpool else "",
|
||||
dmgType.capitalize(),
|
||||
formatAmount(val / normal.total * 100, 3, 0, 0)))
|
||||
return "\n".join(lines)
|
||||
|
||||
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
|
||||
stats = (
|
||||
(
|
||||
"labelFullDpsWeapon",
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
|
||||
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
|
||||
3, 0, 0, "{}{} DPS"),
|
||||
(
|
||||
"labelFullDpsDrone",
|
||||
lambda: fit.getDroneDps().total,
|
||||
lambda: fit.getDroneDps().total,
|
||||
lambda: fit.getDroneDps().total,
|
||||
lambda: fit.getDroneDps(),
|
||||
lambda: fit.getDroneDps(),
|
||||
lambda: fit.getDroneDps(),
|
||||
3, 0, 0, "{}{} DPS"),
|
||||
(
|
||||
"labelFullVolleyTotal",
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
|
||||
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
|
||||
3, 0, 0, "{}{}"),
|
||||
(
|
||||
"labelFullDpsTotal",
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
|
||||
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
|
||||
3, 0, 0, "{}{}"))
|
||||
|
||||
counter = 0
|
||||
for labelName, val, preSpoolVal, fullSpoolVal, prec, lowest, highest, valueFormat in stats:
|
||||
label = getattr(self, labelName)
|
||||
val = val() if fit is not None else 0
|
||||
preSpoolVal = preSpoolVal() if fit is not None else 0
|
||||
fullSpoolVal = fullSpoolVal() if fit is not None else 0
|
||||
val = val() if fit is not None else None
|
||||
preSpoolVal = preSpoolVal() if fit is not None else None
|
||||
fullSpoolVal = fullSpoolVal() if fit is not None else None
|
||||
if self._cachedValues[counter] != val:
|
||||
tooltipText = dpsToolTip(preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
tooltipText = dpsToolTip(val, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat.format(
|
||||
formatAmount(val, prec, lowest, highest),
|
||||
"\u02e2" if tooltipText else ""))
|
||||
formatAmount(0 if val is None else val.total, prec, lowest, highest),
|
||||
"\u02e2" if hasSpoolUp(preSpoolVal, fullSpoolVal) else ""))
|
||||
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||
self._cachedValues[counter] = val
|
||||
counter += 1
|
||||
|
||||
@@ -111,7 +111,7 @@ class BaseName(ViewColumn):
|
||||
elif isinstance(stuff, Implant):
|
||||
return stuff.item.name
|
||||
elif isinstance(stuff, TargetProfile):
|
||||
return stuff.name
|
||||
return stuff.shortName
|
||||
else:
|
||||
item = getattr(stuff, "item", stuff)
|
||||
|
||||
|
||||
60
gui/builtinViewColumns/dampScanRes.py
Normal file
60
gui/builtinViewColumns/dampScanRes.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from eos.saveddata.fit import Fit
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from eos.utils.float import floatUnerr
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
class DampScanResColumn(ViewColumn):
|
||||
|
||||
name = 'Damp ScanRes'
|
||||
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
self.imageId = fittingView.imageList.GetImageIndex(74, 'icons')
|
||||
self.bitmap = BitmapLoader.getBitmap(74, 'icons')
|
||||
self.mask = wx.LIST_MASK_IMAGE
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
mult = 1
|
||||
if isinstance(stuff, Fit):
|
||||
mult = floatUnerr(stuff.getDampMultScanRes())
|
||||
if mult == 1:
|
||||
text = ''
|
||||
else:
|
||||
text = '{}%'.format(formatAmount((mult - 1) * 100, 3, 0, 0, forceSign=True))
|
||||
return text
|
||||
|
||||
def getImageId(self, stuff):
|
||||
return -1
|
||||
|
||||
def getToolTip(self, stuff):
|
||||
return 'Scan resolution dampening'
|
||||
|
||||
|
||||
DampScanResColumn.register()
|
||||
@@ -24,7 +24,7 @@ from eos.saveddata.mode import Mode
|
||||
from service.attribute import Attribute
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
|
||||
|
||||
class MaxRange(ViewColumn):
|
||||
@@ -77,7 +77,21 @@ class MaxRange(ViewColumn):
|
||||
return ("displayName", bool, False), ("showIcon", bool, True)
|
||||
|
||||
def getToolTip(self, mod):
|
||||
return "Optimal + Falloff"
|
||||
lines = []
|
||||
missileRangeData = mod.missileMaxRangeData if hasattr(mod, "missileMaxRangeData") else None
|
||||
if missileRangeData is not None:
|
||||
lines.append('Missile flight range')
|
||||
lowerRange, higherRange, higherChance = missileRangeData
|
||||
if roundToPrec(higherChance, 3) not in (0, 1):
|
||||
lines.append('{}% chance to fly {}m'.format(
|
||||
formatAmount((1 - higherChance) * 100, prec=3, lowest=0, highest=0),
|
||||
formatAmount(lowerRange, prec=3, lowest=0, highest=3)))
|
||||
lines.append('{}% chance to fly {}m'.format(
|
||||
formatAmount(higherChance * 100, prec=3, lowest=0, highest=0),
|
||||
formatAmount(higherRange, prec=3, lowest=0, highest=3)))
|
||||
else:
|
||||
lines.append("Optimal + Falloff")
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
MaxRange.register()
|
||||
|
||||
@@ -339,23 +339,25 @@ class Miscellanea(ViewColumn):
|
||||
formatAmount(radar, 3, 0, 3),
|
||||
)
|
||||
return text, tooltip
|
||||
elif itemGroup in ("Remote Sensor Booster", "Sensor Booster", "Signal Amplifier"):
|
||||
elif itemGroup in ("Remote Sensor Booster", "Sensor Booster", "Signal Amplifier", "Structure Signal Amplifier"):
|
||||
textLines = []
|
||||
tooltipLines = []
|
||||
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
|
||||
if scanResBonus:
|
||||
textLines.append("{}%".format(formatAmount(scanResBonus, 3, 0, 3)))
|
||||
tooltipLines.append("{}% scan resolution".format(formatAmount(scanResBonus, 3, 0, 3)))
|
||||
lockRangeBonus = stuff.getModifiedItemAttr("maxTargetRangeBonus")
|
||||
if lockRangeBonus:
|
||||
textLines.append("{}%".format(formatAmount(lockRangeBonus, 3, 0, 3)))
|
||||
tooltipLines.append("{}% lock range".format(formatAmount(lockRangeBonus, 3, 0, 3)))
|
||||
gravBonus = stuff.getModifiedItemAttr("scanGravimetricStrengthPercent")
|
||||
if scanResBonus is None or lockRangeBonus is None or gravBonus is None:
|
||||
if gravBonus:
|
||||
textLines.append("{}%".format(formatAmount(gravBonus, 3, 0, 3)))
|
||||
tooltipLines.append("{}% sensor strength".format(formatAmount(gravBonus, 3, 0, 3)))
|
||||
if not textLines:
|
||||
return "", None
|
||||
|
||||
text = "{0}% | {1}% | {2}%".format(
|
||||
formatAmount(scanResBonus, 3, 0, 3),
|
||||
formatAmount(lockRangeBonus, 3, 0, 3),
|
||||
formatAmount(gravBonus, 3, 0, 3),
|
||||
)
|
||||
tooltip = "Applied bonuses:\n{0}% scan resolution | {1}% lock range | {2}% sensor strength".format(
|
||||
formatAmount(scanResBonus, 3, 0, 3),
|
||||
formatAmount(lockRangeBonus, 3, 0, 3),
|
||||
formatAmount(gravBonus, 3, 0, 3),
|
||||
)
|
||||
text = " | ".join(textLines)
|
||||
tooltip = "Applied bonuses:\n{}".format(" | ".join(tooltipLines))
|
||||
return text, tooltip
|
||||
elif itemGroup in ("Projected ECCM", "ECCM", "Sensor Backup Array"):
|
||||
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthPercent")
|
||||
@@ -588,7 +590,7 @@ class Miscellanea(ViewColumn):
|
||||
):
|
||||
if "Armor" in itemGroup or "Shield" in itemGroup:
|
||||
boosted_attribute = "HP"
|
||||
reload_time = item.getAttribute("reloadTime", 0) / 1000
|
||||
reload_time = stuff.getModifiedItemAttr("reloadTime", 0) / 1000
|
||||
elif "Capacitor" in itemGroup:
|
||||
boosted_attribute = "Cap"
|
||||
reload_time = 10
|
||||
|
||||
@@ -178,10 +178,17 @@ class EntityEditor(wx.Panel):
|
||||
return True
|
||||
|
||||
def checkEntitiesExist(self):
|
||||
if len(self.choices) == 0:
|
||||
self.Parent.Hide()
|
||||
if self.OnNew(None) is False:
|
||||
return False
|
||||
self.Parent.Show()
|
||||
if len(self.choices) > 0:
|
||||
return True
|
||||
else:
|
||||
return self.enterNewEntity()
|
||||
|
||||
def enterNewEntity(self):
|
||||
self.Parent.Hide()
|
||||
if self.OnNew(None) is False:
|
||||
return False
|
||||
self.Parent.Show()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ class FitSpawner(gui.multiSwitch.TabSpawner):
|
||||
self.multiSwitch.SetSelection(index)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(event.fitID,)))
|
||||
break
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.critical("Caught exception in fitSelected")
|
||||
pyfalog.critical(e)
|
||||
@@ -468,6 +470,8 @@ class FittingView(d.Display):
|
||||
elif item.isModule:
|
||||
if mstate.GetModifiers() == wx.MOD_ALT:
|
||||
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID))
|
||||
elif dstPos is not None:
|
||||
self.mainFrame.command.Submit(cmd.GuiReplaceLocalModuleCommand(fitID=fitID, itemID=itemID, positions=[dstPos]))
|
||||
else:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID))
|
||||
elif item.isSubsystem:
|
||||
@@ -810,6 +814,8 @@ class FittingView(d.Display):
|
||||
if self and not self.IsShown():
|
||||
try:
|
||||
self.MakeSnapshot()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.critical("Failed to make snapshot")
|
||||
pyfalog.critical(e)
|
||||
@@ -835,6 +841,8 @@ class FittingView(d.Display):
|
||||
sFit = Fit.getInstance()
|
||||
try:
|
||||
fit = sFit.getFit(self.activeFitID)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.critical("Failed to get fit")
|
||||
pyfalog.critical(e)
|
||||
|
||||
@@ -309,7 +309,7 @@ class ItemView(d.Display):
|
||||
self.clearSearch()
|
||||
return
|
||||
|
||||
sMkt.searchItems(search, self.populateSearch, ["Implant"])
|
||||
sMkt.searchItems(search, self.populateSearch, 'implants')
|
||||
|
||||
def populateSearch(self, items):
|
||||
if not self.IsShown():
|
||||
|
||||
@@ -428,6 +428,31 @@ class SkillTreeView(wx.Panel):
|
||||
# This cuases issues with GTK, see #1866
|
||||
# self.Layout()
|
||||
|
||||
# For level keyboard shortcuts
|
||||
self.ChangeLevelEvent, CHANGE_LEVEL_EVENT = wx.lib.newevent.NewEvent()
|
||||
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
|
||||
self.Bind(CHANGE_LEVEL_EVENT, self.changeLevel)
|
||||
|
||||
def kbEvent(self, event):
|
||||
keyLevelMap = {
|
||||
# Regular number keys
|
||||
48: 0, 49: 1, 50: 2, 51: 3, 52: 4, 53: 5,
|
||||
# Numpad keys
|
||||
wx.WXK_NUMPAD0: 0, wx.WXK_NUMPAD1: 1, wx.WXK_NUMPAD2: 2,
|
||||
wx.WXK_NUMPAD3: 3, wx.WXK_NUMPAD4: 4, wx.WXK_NUMPAD5: 5}
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode in keyLevelMap and event.GetModifiers() == wx.MOD_NONE:
|
||||
level = keyLevelMap[keycode]
|
||||
selection = self.skillTreeListCtrl.GetSelection()
|
||||
if selection:
|
||||
dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)
|
||||
if dataType == 'skill':
|
||||
event = self.ChangeLevelEvent()
|
||||
event.SetId(self.idLevels[level])
|
||||
wx.PostEvent(self, event)
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
def importSkills(self, evt):
|
||||
|
||||
with wx.MessageDialog(
|
||||
@@ -450,6 +475,8 @@ class SkillTreeView(wx.Panel):
|
||||
if skill:
|
||||
skill.setLevel(level, ignoreRestrict=True)
|
||||
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.error(e)
|
||||
with wx.MessageDialog(self, "There was an error importing skills, please see log file", "Error", wx.ICON_ERROR) as dlg:
|
||||
@@ -611,6 +638,8 @@ class SkillTreeView(wx.Panel):
|
||||
|
||||
sChar = Character.getInstance()
|
||||
char = self.charEditor.entityEditor.getActiveEntity()
|
||||
if char.name in ("All 0", "All 5"):
|
||||
return
|
||||
selection = self.skillTreeListCtrl.GetSelection()
|
||||
dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)
|
||||
|
||||
@@ -812,7 +841,7 @@ class APIView(wx.Panel):
|
||||
|
||||
def getActiveCharacter(self):
|
||||
selection = self.charChoice.GetCurrentSelection()
|
||||
return self.charChoice.GetClientData(selection) if selection is not -1 else None
|
||||
return self.charChoice.GetClientData(selection) if selection != -1 else None
|
||||
|
||||
def ssoListChanged(self, event):
|
||||
if not self: # todo: fix event not unbinding properly
|
||||
|
||||
@@ -122,7 +122,7 @@ class CharacterSelection(wx.Panel):
|
||||
|
||||
def getActiveCharacter(self):
|
||||
selection = self.charChoice.GetCurrentSelection()
|
||||
return self.charChoice.GetClientData(selection) if selection is not -1 else None
|
||||
return self.charChoice.GetClientData(selection) if selection != -1 else None
|
||||
|
||||
def refreshCharacterList(self, event=None):
|
||||
choice = self.charChoice
|
||||
@@ -303,7 +303,6 @@ class CharacterSelection(wx.Panel):
|
||||
if tabulationLevel == 0:
|
||||
for item, subReqs in reqs.items():
|
||||
skillsMap = self._buildSkillsTooltipCondensed(subReqs, item.name, 1, skillsMap)
|
||||
sorted(skillsMap, key=skillsMap.get)
|
||||
else:
|
||||
for name, info in reqs.items():
|
||||
level, ID, more = info
|
||||
@@ -323,3 +322,25 @@ class CharacterSelection(wx.Panel):
|
||||
skillsMap = self._buildSkillsTooltipCondensed(more, currItem, tabulationLevel + 1, skillsMap)
|
||||
|
||||
return skillsMap
|
||||
|
||||
def _buildSkillsTooltipSuperCondensed(self, reqs, currItem="", tabulationLevel=0, skillsMap=None):
|
||||
allReqs = {}
|
||||
implicitReqs = {}
|
||||
|
||||
def traverseReqs(itemReqs, topLevel=True):
|
||||
for skillName, (skillLevel, skillTypeID, subReqs) in itemReqs.items():
|
||||
if (skillTypeID, skillName) not in allReqs or allReqs[(skillTypeID, skillName)] < skillLevel:
|
||||
allReqs[(skillTypeID, skillName)] = skillLevel
|
||||
if not topLevel and (skillTypeID not in implicitReqs or implicitReqs[skillTypeID] < skillLevel):
|
||||
implicitReqs[skillTypeID] = skillLevel
|
||||
traverseReqs(subReqs, topLevel=False)
|
||||
|
||||
for item, itemReqs in reqs.items():
|
||||
traverseReqs(itemReqs)
|
||||
|
||||
newReqs = {}
|
||||
for (skillTypeID, skillName), skillLevel in allReqs.items():
|
||||
if skillTypeID not in implicitReqs or implicitReqs[skillTypeID] < skillLevel:
|
||||
newReqs[skillName] = skillLevel, skillTypeID
|
||||
|
||||
return newReqs
|
||||
|
||||
@@ -1138,6 +1138,8 @@ class _TabsContainer(wx.Panel):
|
||||
self.preview_tab = tab
|
||||
self.preview_timer.Start(500, True)
|
||||
break
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ from abc import ABCMeta, abstractmethod
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -32,6 +34,7 @@ class ContextMenu(metaclass=ABCMeta):
|
||||
menus = []
|
||||
_ids = [] # [wx.NewId() for x in xrange(200)] # init with decent amount
|
||||
_idxid = -1
|
||||
visibilitySetting = None
|
||||
|
||||
@classmethod
|
||||
def register(cls):
|
||||
@@ -72,6 +75,10 @@ class ContextMenu(metaclass=ABCMeta):
|
||||
ContextMenu._idxid = -1
|
||||
debug_start = len(ContextMenu._ids)
|
||||
|
||||
# Control being pressed forces all hidden menu items to be shown
|
||||
visibilitySettingOverride = wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL
|
||||
cmSettings = ContextMenuSettings.getInstance()
|
||||
|
||||
rootMenu = wx.Menu()
|
||||
rootMenu.info = {}
|
||||
rootMenu.selection = (selection,) if not hasattr(selection, "__iter__") else selection
|
||||
@@ -87,7 +94,11 @@ class ContextMenu(metaclass=ABCMeta):
|
||||
for menuHandler in cls.menus:
|
||||
# loop through registered menus
|
||||
m = menuHandler()
|
||||
if m._baseDisplay(callingWindow, srcContext, mainItem, selection):
|
||||
if m.visibilitySetting:
|
||||
visible = visibilitySettingOverride or cmSettings.get(m.visibilitySetting)
|
||||
else:
|
||||
visible = True
|
||||
if visible and m._baseDisplay(callingWindow, srcContext, mainItem, selection):
|
||||
display_amount += 1
|
||||
texts = m._baseGetText(callingWindow, itemContext, mainItem, selection)
|
||||
|
||||
@@ -182,12 +193,12 @@ class ContextMenu(metaclass=ABCMeta):
|
||||
return ContextMenu._ids[ContextMenu._idxid]
|
||||
|
||||
def isChecked(self, i):
|
||||
'''If menu item is toggleable, this should return bool value'''
|
||||
"""If menu item is toggleable, this should return bool value"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
'''If menu item is enabled. Allows an item to display, but not be selected'''
|
||||
"""If menu item is enabled. Allows an item to display, but not be selected"""
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -40,6 +40,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
copyFormatEsi = 3
|
||||
copyFormatMultiBuy = 4
|
||||
copyFormatEfs = 5
|
||||
copyFormatFitStats = 6
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
@@ -50,7 +51,8 @@ class CopySelectDialog(wx.Dialog):
|
||||
CopySelectDialog.copyFormatDna : self.exportDna,
|
||||
CopySelectDialog.copyFormatEsi : self.exportEsi,
|
||||
CopySelectDialog.copyFormatMultiBuy: self.exportMultiBuy,
|
||||
CopySelectDialog.copyFormatEfs : self.exportEfs
|
||||
CopySelectDialog.copyFormatEfs : self.exportEfs,
|
||||
CopySelectDialog.copyFormatFitStats: self.exportFitStats
|
||||
}
|
||||
|
||||
self.mainFrame = parent
|
||||
@@ -62,6 +64,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
("ESI", (CopySelectDialog.copyFormatEsi, None)),
|
||||
("DNA", (CopySelectDialog.copyFormatDna, DNA_OPTIONS)),
|
||||
("EFS", (CopySelectDialog.copyFormatEfs, None)),
|
||||
("Stats", (CopySelectDialog.copyFormatFitStats, None)),
|
||||
# ("XML", (CopySelectDialog.copyFormatXml, None)),
|
||||
))
|
||||
|
||||
@@ -71,7 +74,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
continue
|
||||
defaultFormatOptions[formatId] = {opt[0]: opt[3] for opt in formatOptions}
|
||||
|
||||
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": 0, "options": defaultFormatOptions})
|
||||
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": self.copyFormatEft, "options": defaultFormatOptions})
|
||||
# Options used to be stored as int (EFT export options only),
|
||||
# overwrite them with new format when needed
|
||||
if isinstance(self.settings["options"], int):
|
||||
@@ -80,6 +83,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
self.options = {}
|
||||
|
||||
initialized = False
|
||||
self.copyFormat = self.copyFormatEft
|
||||
for formatName, formatData in self.copyFormats.items():
|
||||
formatId, formatOptions = formatData
|
||||
if not initialized:
|
||||
@@ -117,7 +121,8 @@ class CopySelectDialog(wx.Dialog):
|
||||
self.Center()
|
||||
|
||||
def Validate(self):
|
||||
# Since this dialog is shown through aa ShowModal(), we hook into the Validate function to veto the closing of the dialog until we're ready.
|
||||
# Since this dialog is shown through as ShowModal(),
|
||||
# we hook into the Validate function to veto the closing of the dialog until we're ready.
|
||||
# This always returns False, and when we're ready will EndModal()
|
||||
selected = self.GetSelected()
|
||||
options = self.GetOptions()
|
||||
@@ -185,3 +190,10 @@ class CopySelectDialog(wx.Dialog):
|
||||
def exportEfs(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
EfsPort.exportEfs(fit, 0, callback)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def exportFitStats(self, options, callback):
|
||||
""" Puts fit stats in textual format into the clipboard """
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportFitStats(fit, callback)
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@ class EveFittings(AuxiliaryFrame):
|
||||
# Can't do this in a finally because then it obscures the message dialog
|
||||
del waitDialog # noqa: F821
|
||||
ESIExceptionHandler(self, ex)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as ex:
|
||||
del waitDialog # noqa: F821
|
||||
raise ex
|
||||
@@ -302,6 +304,8 @@ class ExportToEve(AuxiliaryFrame):
|
||||
except APIException as ex:
|
||||
try:
|
||||
ESIExceptionHandler(self, ex)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as ex:
|
||||
self.statusbar.SetStatusText("ERROR", 0)
|
||||
self.statusbar.SetStatusText("{} - {}".format(res.status_code, res.reason), 1)
|
||||
@@ -381,6 +385,8 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
try:
|
||||
sEsi = Esi.getInstance()
|
||||
sEsi.login()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as ex:
|
||||
ESIServerExceptionHandler(self, ex)
|
||||
|
||||
@@ -457,6 +463,8 @@ class FittingsTreeView(wx.Panel):
|
||||
cargo = Cargo(getItem(item['type_id']))
|
||||
cargo.amount = item['quantity']
|
||||
list.append(cargo)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.critical("Exception caught in displayFit")
|
||||
pyfalog.critical(e)
|
||||
|
||||
@@ -321,7 +321,10 @@ def activeStateLimit(itemIdentity):
|
||||
item = Market.getInstance().getItem(itemIdentity)
|
||||
if {
|
||||
'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
|
||||
'microJumpDrive', 'microJumpPortalDrive'
|
||||
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
|
||||
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
|
||||
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
|
||||
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively'
|
||||
}.intersection(item.effects):
|
||||
return FittingModuleState.ONLINE
|
||||
return FittingModuleState.ACTIVE
|
||||
|
||||
@@ -99,12 +99,14 @@ class PFPanel(wx.Panel):
|
||||
|
||||
|
||||
class OpenFitsThread(threading.Thread):
|
||||
|
||||
def __init__(self, fits, callback):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "LoadingOpenFits"
|
||||
self.mainFrame = MainFrame.getInstance()
|
||||
self.callback = callback
|
||||
self.fits = fits
|
||||
self.running = True
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
@@ -118,10 +120,15 @@ class OpenFitsThread(threading.Thread):
|
||||
# We use 1 for all fits except the last one where we use 2 so that we
|
||||
# have correct calculations displayed at startup
|
||||
for fitID in self.fits[:-1]:
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1))
|
||||
if self.running:
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID, startup=1))
|
||||
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
|
||||
wx.CallAfter(self.callback)
|
||||
if self.running:
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fits[-1], startup=2))
|
||||
wx.CallAfter(self.callback)
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
|
||||
|
||||
# todo: include IPortUser again
|
||||
@@ -204,6 +211,7 @@ class MainFrame(wx.Frame):
|
||||
self.addPageId = wx.NewId()
|
||||
self.closePageId = wx.NewId()
|
||||
self.closeAllPagesId = wx.NewId()
|
||||
self.hiddenGraphsId = wx.NewId()
|
||||
|
||||
self.widgetInspectMenuID = wx.NewId()
|
||||
self.SetMenuBar(MainMenuBar(self))
|
||||
@@ -250,10 +258,12 @@ class MainFrame(wx.Frame):
|
||||
fit = sFit.getFit(id, basic=True)
|
||||
if fit is None:
|
||||
fits.remove(id)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
fits.remove(id)
|
||||
|
||||
if not self.prevOpenFits['enabled'] or len(fits) is 0:
|
||||
if not self.prevOpenFits['enabled'] or len(fits) == 0:
|
||||
# add blank page if there are no fits to be loaded
|
||||
self.fitMultiSwitch.AddPage()
|
||||
return
|
||||
@@ -423,6 +433,9 @@ class MainFrame(wx.Frame):
|
||||
def OnShowGraphFrame(self, event):
|
||||
GraphFrame.openOne(self)
|
||||
|
||||
def OnShowGraphFrameHidden(self, event):
|
||||
GraphFrame.openOne(self, includeHidden=True)
|
||||
|
||||
def OnShowDevTools(self, event):
|
||||
DevTools.openOne(parent=self)
|
||||
|
||||
@@ -551,6 +564,7 @@ class MainFrame(wx.Frame):
|
||||
|
||||
# Graphs
|
||||
self.Bind(wx.EVT_MENU, self.OnShowGraphFrame, id=menuBar.graphFrameId)
|
||||
self.Bind(wx.EVT_MENU, self.OnShowGraphFrameHidden, id=self.hiddenGraphsId)
|
||||
|
||||
toggleSearchBoxId = wx.NewId()
|
||||
toggleShipMarketId = wx.NewId()
|
||||
@@ -576,6 +590,9 @@ class MainFrame(wx.Frame):
|
||||
(wx.ACCEL_CTRL, wx.WXK_F4, self.closePageId),
|
||||
(wx.ACCEL_CMD, ord("W"), self.closePageId),
|
||||
|
||||
(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId),
|
||||
(wx.ACCEL_CMD | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId),
|
||||
|
||||
(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId),
|
||||
(wx.ACCEL_CTRL | wx.ACCEL_ALT, wx.WXK_F4, self.closeAllPagesId),
|
||||
(wx.ACCEL_CMD | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId),
|
||||
@@ -739,9 +756,12 @@ class MainFrame(wx.Frame):
|
||||
activeFit = self.getActiveFit()
|
||||
try:
|
||||
importType, importData = Port().importFitFromBuffer(clipboard, activeFit)
|
||||
if importType == "MutatedItem":
|
||||
# we've imported an Abyssal module, need to fire off the command to add it to the fit
|
||||
self.command.Submit(cmd.GuiImportLocalMutatedModuleCommand(activeFit, *importData[0]))
|
||||
if importType == "FittingItem":
|
||||
baseItem, mutaplasmidItem, mutations = importData[0]
|
||||
if mutaplasmidItem:
|
||||
self.command.Submit(cmd.GuiImportLocalMutatedModuleCommand(activeFit, baseItem, mutaplasmidItem, mutations))
|
||||
else:
|
||||
self.command.Submit(cmd.GuiAddLocalModuleCommand(activeFit, baseItem.ID))
|
||||
return
|
||||
if importType == "AdditionsDrones":
|
||||
if self.command.Submit(cmd.GuiImportLocalDronesCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
|
||||
@@ -763,6 +783,8 @@ class MainFrame(wx.Frame):
|
||||
if self.command.Submit(cmd.GuiImportCargosCommand(activeFit, [(i.ID, a) for i, a in importData[0]])):
|
||||
self.additionsPane.select("Cargo")
|
||||
return
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pyfalog.error("Attempt to import failed:\n{0}", clipboard)
|
||||
else:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user