Compare commits
141 Commits
v2.16.4dev
...
v2.20.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
855fafa94d | ||
|
|
4e10335ae7 | ||
|
|
21ea9ce579 | ||
|
|
ea07bbf4f9 | ||
|
|
8eed6fbe21 | ||
|
|
0859f2fbe9 | ||
|
|
71ba33edeb | ||
|
|
ce80d92b35 | ||
|
|
f17cf9b736 | ||
|
|
98579c774b | ||
|
|
509fa279e7 | ||
|
|
091ee87761 | ||
|
|
c0c20cc92e | ||
|
|
1341f7bca1 | ||
|
|
fe93db1d4b | ||
|
|
5db97ea773 | ||
|
|
1758e4f320 | ||
|
|
1a897c0419 | ||
|
|
32db3e3179 | ||
|
|
d830a8957a | ||
|
|
652ea48223 | ||
|
|
8c25b2b8f5 | ||
|
|
db4c56be8e | ||
|
|
f3bcffe2f9 | ||
|
|
bc5786d099 | ||
|
|
5959fe5daf | ||
|
|
649d338bb1 | ||
|
|
dcb058a718 | ||
|
|
1772bb5e7f | ||
|
|
30bd0adb06 | ||
|
|
44dfcf771c | ||
|
|
a1f8a7a930 | ||
|
|
b22887dfad | ||
|
|
28137fa3f4 | ||
|
|
9cbdc6055d | ||
|
|
fc93c61fcf | ||
|
|
3fa2e7ebd1 | ||
|
|
818628da0c | ||
|
|
adf90a8263 | ||
|
|
362923ac64 | ||
|
|
7d73838ce1 | ||
|
|
b3278ca9ec | ||
|
|
5707914ad5 | ||
|
|
9b697b24d8 | ||
|
|
098b088da6 | ||
|
|
14d62f31e1 | ||
|
|
e7b1f55d08 | ||
|
|
8f501896a1 | ||
|
|
befcb9b874 | ||
|
|
c70afa9a4c | ||
|
|
029c7dd4c2 | ||
|
|
1f83dba1ac | ||
|
|
6eae3405fd | ||
|
|
c0e1d9e4de | ||
|
|
7c0bd7aa88 | ||
|
|
394583584c | ||
|
|
4112e2aa6b | ||
|
|
aa19e0da72 | ||
|
|
bba9be1598 | ||
|
|
17998916b4 | ||
|
|
543089bcd9 | ||
|
|
7f35c78a65 | ||
|
|
b25798dd83 | ||
|
|
744b9ff78a | ||
|
|
8dc1457ebb | ||
|
|
982ad54fab | ||
|
|
912192cd7d | ||
|
|
74cd6d48da | ||
|
|
3467a7fe3f | ||
|
|
eb269a05ed | ||
|
|
c63cf4b3b0 | ||
|
|
11ed94454d | ||
|
|
8df07645da | ||
|
|
5666fdd250 | ||
|
|
0b55ae40fc | ||
|
|
3726e84697 | ||
|
|
a0c4341102 | ||
|
|
cd6b1038e8 | ||
|
|
10583fd506 | ||
|
|
713694be56 | ||
|
|
f50293cf77 | ||
|
|
7c88fa477f | ||
|
|
f8df540fad | ||
|
|
61a01805cc | ||
|
|
a93915cf04 | ||
|
|
0f74c97fbf | ||
|
|
29713b69dc | ||
|
|
cc3c2cb9c8 | ||
|
|
b36a3959da | ||
|
|
3b26bfc0e9 | ||
|
|
c59b621963 | ||
|
|
977a8fa329 | ||
|
|
a5226fee83 | ||
|
|
e4069a3988 | ||
|
|
6527f9e11e | ||
|
|
9ddfcc894f | ||
|
|
f22a4f13e5 | ||
|
|
fc9337df67 | ||
|
|
98a8332cfa | ||
|
|
a78eedad48 | ||
|
|
85e1a1bd06 | ||
|
|
25b2c04309 | ||
|
|
54a84d0e42 | ||
|
|
af0a4d1def | ||
|
|
97cdde84ce | ||
|
|
a65129b277 | ||
|
|
3b40a49918 | ||
|
|
d95345b476 | ||
|
|
2ba2d95017 | ||
|
|
4d0ffedcc8 | ||
|
|
84abde4fc5 | ||
|
|
8897f1e4b1 | ||
|
|
6f33cacb7a | ||
|
|
b1a8c0ad09 | ||
|
|
13ed635803 | ||
|
|
5a44909ebf | ||
|
|
6cbb80693d | ||
|
|
0b90d254f9 | ||
|
|
641d36205c | ||
|
|
6e38b6ea4d | ||
|
|
84b4d8fabb | ||
|
|
c0e1f7e746 | ||
|
|
d734c12168 | ||
|
|
340d94eb3e | ||
|
|
9b0b31648c | ||
|
|
8346358c37 | ||
|
|
b935700a12 | ||
|
|
2f4a5a4830 | ||
|
|
4d4680961e | ||
|
|
1cae99b812 | ||
|
|
3727d19311 | ||
|
|
c45c093f4f | ||
|
|
b613d3e312 | ||
|
|
4cff9247b0 | ||
|
|
b9a26ec28d | ||
|
|
245f81e888 | ||
|
|
204c1ba9f2 | ||
|
|
a7a4be133c | ||
|
|
14f9a46d7a | ||
|
|
003dd040dc | ||
|
|
97cf53217e |
123
.appveyor.yml
@@ -1,126 +1,78 @@
|
||||
image: Visual Studio 2019
|
||||
clone_depth: 400
|
||||
|
||||
environment:
|
||||
global:
|
||||
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
|
||||
# /E:ON and /V:ON options are not enabled in the batch script intepreter
|
||||
# See: http://stackoverflow.com/a/13751649/163740
|
||||
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
|
||||
|
||||
matrix:
|
||||
|
||||
- PYTHON: "C:\\Python36"
|
||||
PYTHON_VERSION: "3.6.x"
|
||||
PYTHON_ARCH: "32"
|
||||
init:
|
||||
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
- PYTHON: "C:\\Python37-x64"
|
||||
# Should be enabled only for build process debugging
|
||||
# init:
|
||||
# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
install:
|
||||
# If there is a newer build queued for the same PR, cancel this one.
|
||||
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
||||
# purpose but it is problematic because it tends to cancel builds pushed
|
||||
# directly to master instead of just PR builds (or the converse).
|
||||
# credits: JuliaLang developers.
|
||||
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
|
||||
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
throw "There are newer queued builds for this pull request, failing early." }
|
||||
- ps: echo("OS version:")
|
||||
- ps: "[System.Environment]::OSVersion.Version"
|
||||
|
||||
- ECHO "Filesystem root:"
|
||||
- ps: "ls \"C:/\""
|
||||
- ps: echo("Filesystem - root:")
|
||||
- ps: "ls \"C:\\\""
|
||||
|
||||
- ECHO "Filesystem projects root:"
|
||||
- ps: echo("Filesystem - projects root:")
|
||||
- ps: "ls \"C:\\projects\\\""
|
||||
|
||||
- ECHO "Filesystem pyfa root:"
|
||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\""
|
||||
- ps: echo("Filesystem - pyfa root:")
|
||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\\""
|
||||
|
||||
- ECHO "Installed SDKs:"
|
||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
||||
- ps: echo("Filesystem - installed SDKs:")
|
||||
- ps: "ls \"C:\\Program Files (x86)\\Windows Kits\\\""
|
||||
|
||||
# Prepend newly installed Python to the PATH of this build (this cannot be
|
||||
# done from inside the powershell script as it would require to restart
|
||||
# the parent CMD process).
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
- cmd: "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
|
||||
- "python --version"
|
||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
- cmd: "python --version"
|
||||
- cmd: "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||
|
||||
# Upgrade to the latest version of pip to avoid it displaying warnings
|
||||
# about it being out of date.
|
||||
- "python -m 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==3.3"
|
||||
- ps: echo("Install pip requirements:")
|
||||
# This one is needed to build wxpython 4.0.6 on windows
|
||||
- cmd: "python -m pip install pathlib2"
|
||||
- cmd: "python -m pip install -r requirements.txt"
|
||||
- cmd: "python -m pip install PyInstaller==3.6"
|
||||
|
||||
before_build:
|
||||
# directory that will contain the built files
|
||||
- ps: $env:PYFA_DIST_DIR = "c:\projects\$env:APPVEYOR_PROJECT_SLUG\dist"
|
||||
- ps: $env:PYFA_VERSION = (python ./scripts/dump_version.py)
|
||||
- ps: echo("pyfa version ")
|
||||
- ps: echo ($env:PYFA_VERSION)
|
||||
- ps: echo("pyfa version $env:PYFA_VERSION")
|
||||
|
||||
build_script:
|
||||
- ECHO "Build pyfa:"
|
||||
|
||||
- ps: echo("Build pyfa:")
|
||||
# Build gamedata DB
|
||||
- "python db_update.py"
|
||||
##########
|
||||
# PyInstaller - create binaries for pyfa
|
||||
##########
|
||||
- cmd: "python db_update.py"
|
||||
# Build command for PyInstaller
|
||||
- "python -m PyInstaller --noupx --clean --windowed --noconsole -y pyfa.spec"
|
||||
- cmd: "python -m PyInstaller --noupx --clean --windowed --noconsole -y pyfa.spec"
|
||||
# Copy over manifest (See pyfa-org/pyfa#1622)
|
||||
- ps: xcopy /y dist_assets\win\pyfa.exe.manifest $env:PYFA_DIST_DIR\pyfa\
|
||||
# Not really sure if this is needed, but why not
|
||||
- ps: xcopy /y dist_assets\win\Microsoft.VC90.CRT.manifest $env:PYFA_DIST_DIR\pyfa\
|
||||
|
||||
##########
|
||||
# InnoScript EXE building
|
||||
# This is in a separate script because I don't feel like copying over the logic to AppVeyor script right now...
|
||||
##########
|
||||
- "python dist_assets/win/dist.py"
|
||||
# InnoScript EXE building. This is in a separate script because I don't feel like copying over the logic to AppVeyor script right now...
|
||||
- cmd: "python dist_assets/win/dist.py"
|
||||
- ps: dir $env:PYFA_DIST_DIR/
|
||||
#- ECHO "Build pyfa (Debug):"
|
||||
#- copy C:\projects\pyfa\dist_assets\win\pyfa_debug.spec C:\projects\pyfa\pyfa_debug.spec
|
||||
#- "pyinstaller.exe --clean --noconfirm --windowed --upx-dir=C:\\projects\\pyfa\\scripts\\upx.exe C:\\projects\\pyfa\\pyfa_debug.spec"
|
||||
|
||||
build: on
|
||||
|
||||
after_build:
|
||||
- ps: "ls \"./\""
|
||||
#- ps: "ls \"C:\\projects\\pyfa\\build\\pyfa\\\""
|
||||
# - ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\build\\exe.win32-2.7\\\""
|
||||
# Zip
|
||||
# APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER
|
||||
#- 7z a build.zip -r C:\projects\pyfa\build\pyfa\*.*
|
||||
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*.*"
|
||||
#- 7z a pyfa_debug.zip -r C:\projects\pyfa\dist\pyfa_debug\*.*
|
||||
|
||||
on_success:
|
||||
# Do nothing right now
|
||||
- ps: 7z a "pyfa-$env:PYFA_VERSION-win.zip" -r "$env:PYFA_DIST_DIR\pyfa\*"
|
||||
|
||||
test_script:
|
||||
#- tox
|
||||
#- "py.test --cov=./"
|
||||
# Run the project tests
|
||||
# - "%CMD_IN_ENV% python C:/projects/eve-gnosis/setup.py nosetests"
|
||||
|
||||
after_test:
|
||||
# If tests are successful, create binary packages for the project.
|
||||
# - "%CMD_IN_ENV% python setup.py bdist_wheel"
|
||||
# - "%CMD_IN_ENV% python setup.py bdist_wininst"
|
||||
# - "%CMD_IN_ENV% python setup.py bdist_msi"
|
||||
# - ps: "ls dist"
|
||||
# Ha... we're just building
|
||||
|
||||
artifacts:
|
||||
# Archive the generated packages in the ci.appveyor.com build report.
|
||||
- path: pyfa*-win.zip
|
||||
- path: pyfa*-win.exe
|
||||
#- path: pyfa_debug.zip
|
||||
# name: Pyfa_debug
|
||||
|
||||
deploy:
|
||||
tag: $(pyfa_version)
|
||||
@@ -128,10 +80,9 @@ deploy:
|
||||
description: 'Release description'
|
||||
provider: GitHub
|
||||
auth_token:
|
||||
secure: BfNHO66ff5hVx2O2ORbl49X0U/5h2V2T0IuRZDwm7fd1HvsVluF0wRCbl29oRp1M
|
||||
secure: X+U3hOAMTt7HGXCR/LXaGNF6qyhUXetrjz5+xlWiNJQ3XEdzhZZmHK75m0Hm6qre
|
||||
draft: true
|
||||
force_update: false
|
||||
# deploy on tag push only
|
||||
on:
|
||||
APPVEYOR_REPO_TAG: true # deploy on tag push only
|
||||
#on_success:
|
||||
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
||||
#
|
||||
APPVEYOR_REPO_TAG: true
|
||||
|
||||
@@ -3,13 +3,12 @@ language: python
|
||||
git:
|
||||
depth: 400
|
||||
python:
|
||||
- 3.6
|
||||
- 3.8
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode11.3
|
||||
language: generic
|
||||
env: PYTHON=3.6.1
|
||||
before_install:
|
||||
- bash scripts/setup-osx.sh
|
||||
install:
|
||||
@@ -22,7 +21,7 @@ before_deploy:
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: Xfu0xApoB0zUPLXl29aYUulVC3iA4/3bXQwwADKCfAKZwxgNon4dLbO7Rie5/7Ukf2POL0KwmRaQGN3kOr+XSoIVTE4M5sXxnhiaaLGKQ+48hDizLE6JuXcZGJvkxUaghaTzIdCwHsG7VGBsPfQgfGsjJcfBp8tFNLmRyM/Jpsr8T6BR2MxtBIEUVy8zrOWFNZqnmWrY2pWMsB9fYt3JFNdpqeIgRAYqbBsBcZQ1MngLTi3ztuYS5IaF+lk06RrnBlHmUsJu/5nCvIpvPvD0i2BLZ3Uu0+Fn+8QWUgjJEL9MNseXZMXynu05xd8YRk7Ajc9CUrzQIIbAktyteYp85kE3pUJHmrMLcXhh7nqkwttR5/47Zwa3OLJLJFKBxMx6wY5jFkJjkV08850B7aWrmTFl/Eqc3Q5nZMuiEt3wFRbjxHi9h1mTN/fkxfRRHg8u3ENGPR+ZPiFC3J18qtks/B/hsKjjHvZP1i79OYlET4V/zyLyyQkCbpDaARQANuotLYJyZ7tH+KWEyRsvTi0M9Yev9mNNw6aI4vzh4HfkEhvcvnWnYwckPj1dnjQ573Qpw0Z9wsconoWfHAn+hBDt3+YLMrrFZl++mCRskHH1mZChX3aGMDi49zD0kfxBUkYPOAhguc6PwudBxHUZP+O6T/SoHylff6EizCE/k5dGeAk=
|
||||
secure: D8tBW0kyHlKf/sXS69aIuexsYTx9auY2DzudKFlfcvdzqat4N2XZqZbZCTVd7YVvptQ8Dj0oZ/p3KUxEGpnJZmlTeJL142rpM/qaNd6wOIMy2yUde/aZl+W9JLFNQp7KHutM+MxObYLzJGihx/8YsupmFx6lxgdngGDXtXYZe/ruDIWDs92ShoKJ4vlce9Csm7eGKv7wv6Z6V9sD5FS3E9J8xdWStHxsbrkPBOflmG+uHU09dpEqzUm+ZYROIoTwig1Xbw3fw+gfjmNrfdSU4fAJcVZI1hrgoenZyJbMfhI2Ej/nZdbZgaXcZNF/eUpqOGgbPe1JljqFnHTbexcE+LPBVyAToScsGMpByHhig67DrZ0nk9gSZoC6CPNl5YS6xub+5dncMJ3P5L03DOGYRu4SL9NczbeuQyKuea7+JPP/8VLwfFDSEqbNEAmgzABAzrdfano+VXtuBuE/Tiy5eE7le9hJu6aSQoKW1SA3cUhMsmr2amzdO96sh+PN8FA1oNr45Yuy0pqOj4SUIkb8JUy4th7vgdhljEkSxrHDK1UcHpxUTp+IIUZkZVVk50aH68dQZxGwSTVOeRxpjrTcEf7VCGaM98qxi/ZK4RW6Ewiq0eo0AxwEeB2Zm841lycGPR/406vM9/ZBzv5IhELIdDdVWTk+dGjJBXB8z5hPJOg=
|
||||
file_glob: true
|
||||
file: "dist/pyfa-*.zip"
|
||||
skip_cleanup: true
|
||||
|
||||
@@ -49,6 +49,8 @@ def DBInMemory_test():
|
||||
gamedata_version = gamedata_session.execute(
|
||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'"
|
||||
).fetchone()[0]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
print("Missing gamedata version.")
|
||||
gamedata_version = None
|
||||
|
||||
@@ -233,6 +233,8 @@ def defLogging():
|
||||
# reset=False,
|
||||
)
|
||||
])
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print("Critical error attempting to setup logging. Falling back to console only.")
|
||||
logging_setup = NestedSetup([
|
||||
|
||||
60
db_update.py
@@ -23,6 +23,7 @@ import functools
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
@@ -40,7 +41,7 @@ def db_needs_update():
|
||||
try:
|
||||
with open(os.path.join(JSON_DIR, 'phobos', 'metadata.json')) as f:
|
||||
data_version = next((r['field_value'] for r in json.load(f) if r['field_name'] == 'client_build'))
|
||||
except KeyboardInterrupt:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
# If we have no source data - return None; should not update in this case
|
||||
except:
|
||||
@@ -61,7 +62,7 @@ def db_needs_update():
|
||||
db_schema_version = int(row[0])
|
||||
cursor.close()
|
||||
db.close()
|
||||
except KeyboardInterrupt:
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
print('Error when fetching gamedata DB metadata')
|
||||
@@ -124,6 +125,8 @@ def update_db():
|
||||
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor')
|
||||
):
|
||||
row['published'] = True
|
||||
elif row['typeName'].startswith('Limited Synth '):
|
||||
row['published'] = False
|
||||
|
||||
newData = []
|
||||
for row in data:
|
||||
@@ -167,16 +170,26 @@ def update_db():
|
||||
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]})
|
||||
seenKeys = set()
|
||||
|
||||
def checkKey(key):
|
||||
if key in seenKeys:
|
||||
return False
|
||||
seenKeys.add(key)
|
||||
return True
|
||||
|
||||
for typeData in data:
|
||||
if typeData['typeID'] not in eveTypeIds:
|
||||
continue
|
||||
for row in typeData.get('dogmaAttributes', ()):
|
||||
row['typeID'] = typeData['typeID']
|
||||
newData.append(row)
|
||||
if checkKey((row['typeID'], row['attributeID'])):
|
||||
newData.append(row)
|
||||
for row in eveTypesData:
|
||||
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
|
||||
if attrName in row and checkKey((row['typeID'], attrId)):
|
||||
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
|
||||
|
||||
_addRows(newData, eos.gamedata.Attribute)
|
||||
return newData
|
||||
|
||||
@@ -458,6 +471,38 @@ def update_db():
|
||||
if itemReplacements is not None:
|
||||
item.replacements = ','.join('{}'.format(tid) for tid in sorted(itemReplacements))
|
||||
|
||||
def processImplantSets(eveTypesData):
|
||||
print('composing implant sets')
|
||||
# Includes only implants which can be considered part of sets, not all implants
|
||||
implant_groups = (300, 1730)
|
||||
specials = {'Genolution': ('Genolution Core Augmentation', r'CA-\d+')}
|
||||
implantSets = {}
|
||||
for row in eveTypesData:
|
||||
if not row.get('published'):
|
||||
continue
|
||||
if row.get('groupID') not in implant_groups:
|
||||
continue
|
||||
typeName = row.get('typeName', '')
|
||||
# Regular sets matching
|
||||
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
|
||||
if m:
|
||||
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
|
||||
# Special set matching
|
||||
for setHandle, (setName, implantPattern) in specials.items():
|
||||
pattern = '(?P<set>{}) (?P<implant>{})'.format(setName, implantPattern)
|
||||
m = re.match(pattern, typeName)
|
||||
if m:
|
||||
implantSets.setdefault((None, setHandle), set()).add(row['typeID'])
|
||||
break
|
||||
data = []
|
||||
for (gradeName, setName), implants in implantSets.items():
|
||||
if len(implants) < 2:
|
||||
continue
|
||||
implants = ','.join('{}'.format(tid) for tid in sorted(implants))
|
||||
row = {'setName': setName, 'gradeName': gradeName, 'implants': implants}
|
||||
data.append(row)
|
||||
_addRows(data, eos.gamedata.ImplantSet)
|
||||
|
||||
eveTypesData = processEveTypes()
|
||||
eveGroupsData = processEveGroups()
|
||||
processEveCategories()
|
||||
@@ -476,6 +521,7 @@ def update_db():
|
||||
eos.db.gamedata_session.flush()
|
||||
processReqSkills(eveTypesData)
|
||||
processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData)
|
||||
processImplantSets(eveTypesData)
|
||||
|
||||
# Add schema version to prevent further updates
|
||||
metadata_schema_version = eos.gamedata.MetaData()
|
||||
|
||||
@@ -30,7 +30,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'pkg_resources.py2_warn' # issue 2156
|
||||
]
|
||||
|
||||
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
# This apes hook-matplotlib.backends.py, but REMOVES backends, all but
|
||||
# the ones in the list below.
|
||||
# Courtesy of https://github.com/bpteague/cytoflow/blob/70f9291/packaging/hook-matplotlib.backends.py
|
||||
|
||||
KEEP = ["WXAgg", "WX", "agg"]
|
||||
|
||||
from PyInstaller.compat import is_darwin
|
||||
from PyInstaller.utils.hooks import (
|
||||
eval_statement, exec_statement, logger)
|
||||
|
||||
|
||||
def get_matplotlib_backend_module_names():
|
||||
"""
|
||||
List the names of all matplotlib backend modules importable under the
|
||||
current Python installation.
|
||||
Returns
|
||||
----------
|
||||
list
|
||||
List of the fully-qualified names of all such modules.
|
||||
"""
|
||||
# Statement safely importing a single backend module.
|
||||
import_statement = """
|
||||
import os, sys
|
||||
# Preserve stdout.
|
||||
sys_stdout = sys.stdout
|
||||
try:
|
||||
# Redirect output printed by this importation to "/dev/null", preventing
|
||||
# such output from being erroneously interpreted as an error.
|
||||
with open(os.devnull, 'w') as dev_null:
|
||||
sys.stdout = dev_null
|
||||
__import__('%s')
|
||||
# If this is an ImportError, print this exception's message without a traceback.
|
||||
# ImportError messages are human-readable and require no additional context.
|
||||
except ImportError as exc:
|
||||
sys.stdout = sys_stdout
|
||||
print(exc)
|
||||
# Else, print this exception preceded by a traceback. traceback.print_exc()
|
||||
# prints to stderr rather than stdout and must not be called here!
|
||||
except Exception:
|
||||
sys.stdout = sys_stdout
|
||||
import traceback
|
||||
print(traceback.format_exc())
|
||||
"""
|
||||
|
||||
# List of the human-readable names of all available backends.
|
||||
backend_names = eval_statement(
|
||||
'import matplotlib; print(matplotlib.rcsetup.all_backends)')
|
||||
|
||||
# List of the fully-qualified names of all importable backend modules.
|
||||
module_names = []
|
||||
|
||||
# If the current system is not OS X and the "CocoaAgg" backend is available,
|
||||
# remove this backend from consideration. Attempting to import this backend
|
||||
# on non-OS X systems halts the current subprocess without printing output
|
||||
# or raising exceptions, preventing its reliable detection.
|
||||
if not is_darwin and 'CocoaAgg' in backend_names:
|
||||
backend_names.remove('CocoaAgg')
|
||||
|
||||
# For safety, attempt to import each backend in a unique subprocess.
|
||||
for backend_name in backend_names:
|
||||
if backend_name in KEEP:
|
||||
continue
|
||||
|
||||
module_name = 'matplotlib.backends.backend_%s' % backend_name.lower()
|
||||
stdout = exec_statement(import_statement % module_name)
|
||||
|
||||
# If no output was printed, this backend is importable.
|
||||
if not stdout:
|
||||
module_names.append(module_name)
|
||||
logger.info(' Matplotlib backend "%s": removed' % backend_name)
|
||||
|
||||
return module_names
|
||||
|
||||
# Freeze all importable backends, as PyInstaller is unable to determine exactly
|
||||
# which backends are required by the current program.
|
||||
e=get_matplotlib_backend_module_names()
|
||||
print(e)
|
||||
excludedimports = e
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<noInheritable/>
|
||||
<assemblyIdentity
|
||||
type="win32"
|
||||
name="Microsoft.VC90.CRT"
|
||||
version="9.0.21022.8"
|
||||
processorArchitecture="x86"
|
||||
publicKeyToken="1fc8b3b9a1e18e3b"/>
|
||||
<file name="MSVCR90.DLL"/>
|
||||
<file name="MSVCM90.DLL"/>
|
||||
<file name="MSVCP90.DLL"/>
|
||||
</assembly>
|
||||
@@ -14,7 +14,7 @@ with open("version.yml", 'r') as file:
|
||||
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
||||
|
||||
os.environ["PYFA_VERSION"] = version
|
||||
iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine
|
||||
iscc = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||
|
||||
source = os.path.join(os.environ["PYFA_DIST_DIR"], "pyfa")
|
||||
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
||||
#define MyAppExeName "pyfa.exe"
|
||||
|
||||
; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup
|
||||
#define MajorVersionFlag 2
|
||||
#define MinorVersionFlag 0
|
||||
|
||||
#ifndef MyOutputFile
|
||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
||||
#endif
|
||||
@@ -30,7 +26,6 @@
|
||||
#endif
|
||||
|
||||
[Setup]
|
||||
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
@@ -41,6 +36,9 @@ AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
CloseApplications=yes
|
||||
DefaultDirName={pf}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
AllowNoIcons=yes
|
||||
@@ -49,7 +47,6 @@ OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename={#MyOutputFile}
|
||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||
SolidCompression=yes
|
||||
CloseApplications=yes
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
@@ -83,6 +80,7 @@ Type: files; Name: "{app}\*.pyc"
|
||||
|
||||
[Code]
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function IsAppRunning(const FileName : string): Boolean;
|
||||
var
|
||||
FSWbemLocator: Variant;
|
||||
@@ -99,6 +97,7 @@ begin
|
||||
FSWbemLocator := Unassigned;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
procedure RemoveFromVirtualStore;
|
||||
var
|
||||
VirtualStore,FileName,FilePath:String;
|
||||
@@ -115,6 +114,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function PrepareToInstall(var NeedsRestart: Boolean): String;
|
||||
begin
|
||||
if(IsAppRunning( 'pyfa.exe' )) then
|
||||
@@ -127,54 +127,61 @@ begin
|
||||
end
|
||||
end;
|
||||
|
||||
function GetUninstallString: string;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function GetUninstallString(): String;
|
||||
var
|
||||
sUnInstPath: string;
|
||||
sUnInstPath: String;
|
||||
sUnInstallString: String;
|
||||
begin
|
||||
Result := '';
|
||||
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1'); //Your App GUID/ID
|
||||
sUnInstallString := '';
|
||||
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
|
||||
if not RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||
if not RegQueryStringValue(HKLM32, sUnInstPath, 'UninstallString', sUnInstallString) then
|
||||
RegQueryStringValue(HKCU32, sUnInstPath, 'UninstallString', sUnInstallString);
|
||||
Result := sUnInstallString;
|
||||
end;
|
||||
|
||||
function IsUpgrade: Boolean;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function UnInstallOldVersion(): Integer;
|
||||
var
|
||||
sUnInstallString: String;
|
||||
iResultCode: Integer;
|
||||
begin
|
||||
// Return Values:
|
||||
// 1 - uninstall string is empty
|
||||
// 2 - error executing the UnInstallString
|
||||
// 3 - successfully executed the UnInstallString
|
||||
|
||||
// default return value
|
||||
Result := 0;
|
||||
|
||||
// get the uninstall string of the old app
|
||||
sUnInstallString := GetUninstallString();
|
||||
if sUnInstallString <> '' then begin
|
||||
sUnInstallString := RemoveQuotes(sUnInstallString);
|
||||
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
|
||||
Result := 3
|
||||
else
|
||||
Result := 2;
|
||||
end else
|
||||
Result := 1;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function IsUpgrade(): Boolean;
|
||||
begin
|
||||
Result := (GetUninstallString() <> '');
|
||||
end;
|
||||
|
||||
function InitializeSetup: Boolean;
|
||||
var
|
||||
V: Integer;
|
||||
iResultCode: Integer;
|
||||
sUnInstallString: string;
|
||||
iOldVersionMajor: Cardinal;
|
||||
iOldVersionMinor: Cardinal;
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
begin
|
||||
Result := True; // in case when no previous version is found
|
||||
if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', 'UninstallString') then //Your App GUID/ID
|
||||
if (CurStep=ssInstall) then
|
||||
begin
|
||||
RegQueryDWordValue(HKEY_LOCAL_MACHINE,
|
||||
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1',
|
||||
'MajorVersion', iOldVersionMajor);
|
||||
RegQueryDWordValue(HKEY_LOCAL_MACHINE,
|
||||
'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1',
|
||||
'MinorVersion', iOldVersionMinor);
|
||||
if (iOldVersionMajor < {#MajorVersionFlag}) or ((iOldVersionMajor = {#MajorVersionFlag}) and (iOldVersionMinor < {#MinorVersionFlag})) then // If old version with old structure is installed.
|
||||
if (IsUpgrade()) then
|
||||
begin
|
||||
V := MsgBox(ExpandConstant('An old version of pyfa was detected. Due to recent changes in the application structure, you must uninstall the previous version first. This will not affect your user data (saved fittings, characters, etc.). Do you want to uninstall now?'), mbInformation, MB_YESNO); //Custom Message if App installed
|
||||
if V = IDYES then
|
||||
begin
|
||||
sUnInstallString := GetUninstallString();
|
||||
sUnInstallString := RemoveQuotes(sUnInstallString);
|
||||
Exec(ExpandConstant(sUnInstallString), '', '', SW_SHOW, ewWaitUntilTerminated, iResultCode);
|
||||
Result := True; //if you want to proceed after uninstall
|
||||
//Exit; //if you want to quit after uninstall
|
||||
end
|
||||
else
|
||||
Result := False; //when older version present and not uninstalled
|
||||
UnInstallOldVersion();
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
|
||||
|
||||
@@ -20,7 +20,6 @@ added_files = [
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../dist_assets/win/pyfa.ico', '.'),
|
||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||
(requests.certs.where(), '.'), # is this needed anymore?
|
||||
('../../eve.db', '.'),
|
||||
('../../README.md', '.'),
|
||||
@@ -30,7 +29,8 @@ added_files = [
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'sqlalchemy.ext.baked', # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
'pkg_resources.py2_warn' # issue 2156
|
||||
]
|
||||
|
||||
# Walk directories that do dynamic importing
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
# Note: This script is provided AS-IS for those that may be interested.
|
||||
# pyfa does not currently support pyInstaller (or any other build process) 100% at the moment
|
||||
|
||||
# Command line to build:
|
||||
# (Run from directory where pyfa.py and pyfa.spec lives.)
|
||||
# c:\Python27\scripts\pyinstaller.exe --clean --noconfirm --windowed --upx-dir=.\scripts\upx.exe pyfa.spec
|
||||
|
||||
# Don't forget to change the path to where your pyfa.py and pyfa.spec lives
|
||||
# pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'],
|
||||
|
||||
import os
|
||||
|
||||
block_cipher = None
|
||||
|
||||
added_files = [
|
||||
( 'imgs/gui/*.png', 'imgs/gui' ),
|
||||
( 'imgs/gui/*.gif', 'imgs/gui' ),
|
||||
( 'imgs/icons/*.png', 'imgs/icons' ),
|
||||
( 'imgs/renders/*.png', 'imgs/renders' ),
|
||||
( 'dist_assets/win/pyfa.ico', '.' ),
|
||||
( 'dist_assets/cacert.pem', '.' ),
|
||||
( 'eve.db', '.' ),
|
||||
( 'README.md', '.' ),
|
||||
( 'LICENSE', '.' ),
|
||||
]
|
||||
|
||||
import_these = []
|
||||
|
||||
# Walk eos.effects and add all effects so we can import them properly
|
||||
for root, folders, files in os.walk("eos/effects"):
|
||||
for file_ in files:
|
||||
if file_.endswith(".py") and not file_.startswith("_"):
|
||||
mod_name = "{}.{}".format(
|
||||
root.replace("/", "."),
|
||||
file_.split(".py")[0],
|
||||
)
|
||||
import_these.append(mod_name)
|
||||
|
||||
a = Analysis(
|
||||
['pyfa.py'],
|
||||
pathex=['C:\\projects\\pyfa\\'],
|
||||
binaries=[],
|
||||
datas=added_files,
|
||||
hiddenimports=import_these,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
)
|
||||
|
||||
pyz = PYZ(
|
||||
a.pure,
|
||||
a.zipped_data,
|
||||
cipher=block_cipher,
|
||||
)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
debug=True,
|
||||
console=True,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='pyfa_debug',
|
||||
icon='dist_assets/win/pyfa.ico',
|
||||
onefile=False,
|
||||
)
|
||||
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
onefile=False,
|
||||
name='pyfa_debug',
|
||||
icon='dist_assets/win/pyfa.ico',
|
||||
)
|
||||
@@ -17,24 +17,37 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import MetaData, create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||
|
||||
from . import migration
|
||||
from eos import config
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
pyfalog.info("Initializing database")
|
||||
pyfalog.info("Gamedata connection: {0}", config.gamedata_connectionstring)
|
||||
pyfalog.info("Saveddata connection: {0}", config.saveddata_connectionstring)
|
||||
|
||||
|
||||
class ReadOnlyException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def re_fn(expr, item):
|
||||
try:
|
||||
reg = re.compile(expr, re.IGNORECASE)
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
return reg.search(item) is not None
|
||||
|
||||
|
||||
pyfalog.debug('Initializing gamedata')
|
||||
gamedata_connectionstring = config.gamedata_connectionstring
|
||||
if callable(gamedata_connectionstring):
|
||||
@@ -42,9 +55,26 @@ if callable(gamedata_connectionstring):
|
||||
else:
|
||||
gamedata_engine = create_engine(gamedata_connectionstring, echo=config.debug)
|
||||
|
||||
|
||||
@event.listens_for(gamedata_engine, 'connect')
|
||||
def create_functions(dbapi_connection, connection_record):
|
||||
dbapi_connection.create_function('regexp', 2, re_fn)
|
||||
|
||||
|
||||
gamedata_meta = MetaData()
|
||||
gamedata_meta.bind = gamedata_engine
|
||||
gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)()
|
||||
GamedataSession = scoped_session(sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False))
|
||||
gamedata_session = GamedataSession()
|
||||
|
||||
gamedata_sessions = {threading.get_ident(): gamedata_session}
|
||||
|
||||
|
||||
def get_gamedata_session():
|
||||
thread_id = threading.get_ident()
|
||||
if thread_id not in gamedata_sessions:
|
||||
gamedata_sessions[thread_id] = GamedataSession()
|
||||
return gamedata_sessions[thread_id]
|
||||
|
||||
|
||||
pyfalog.debug('Getting gamedata version')
|
||||
# This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new
|
||||
@@ -56,6 +86,8 @@ try:
|
||||
config.gamedata_date = gamedata_session.execute(
|
||||
"SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'dump_time'"
|
||||
).fetchone()[0]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.warning("Missing gamedata version.")
|
||||
pyfalog.critical(e)
|
||||
@@ -82,7 +114,7 @@ sd_lock = threading.RLock()
|
||||
pyfalog.debug('Importing gamedata DB scheme')
|
||||
# Import all the definitions for all our database stuff
|
||||
# noinspection PyPep8
|
||||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
|
||||
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes, implantSet
|
||||
pyfalog.debug('Importing saveddata DB scheme')
|
||||
# noinspection PyPep8
|
||||
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
|
||||
"item", "marketGroup", "metaGroup", "unit", "alphaClones"]
|
||||
"item", "marketGroup", "metaGroup", "unit", "alphaClones", "implantSet"]
|
||||
|
||||
33
eos/db/gamedata/implantSet.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Column, String, Integer, Table
|
||||
from sqlalchemy.orm import mapper, synonym
|
||||
|
||||
from eos.db import gamedata_meta
|
||||
from eos.gamedata import ImplantSet
|
||||
|
||||
implant_set_table = Table("implantsets", gamedata_meta,
|
||||
Column("setID", Integer, primary_key=True),
|
||||
Column("setName", String),
|
||||
Column("gradeName", String),
|
||||
Column("implants", String))
|
||||
|
||||
mapper(ImplantSet, implant_set_table,
|
||||
properties={"ID": synonym("setID")})
|
||||
@@ -33,9 +33,6 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("description", String),
|
||||
Column("raceID", Integer),
|
||||
Column("factionID", Integer),
|
||||
Column("volume", Float),
|
||||
Column("mass", Float),
|
||||
Column("capacity", Float),
|
||||
Column("published", Boolean),
|
||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||
Column("iconID", Integer),
|
||||
|
||||
@@ -22,11 +22,11 @@ from sqlalchemy.orm import aliased, exc, join
|
||||
from sqlalchemy.sql import and_, or_, select
|
||||
|
||||
import eos.config
|
||||
from eos.db import gamedata_session
|
||||
from eos.db import get_gamedata_session
|
||||
from eos.db.gamedata.item import items_table
|
||||
from eos.db.gamedata.group import groups_table
|
||||
from eos.db.util import processEager, processWhere
|
||||
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup
|
||||
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup, ImplantSet
|
||||
|
||||
cache = {}
|
||||
configVal = getattr(eos.config, "gamedataCache", None)
|
||||
@@ -64,7 +64,7 @@ else:
|
||||
return deco
|
||||
|
||||
|
||||
def sqlizeString(line):
|
||||
def sqlizeNormalString(line):
|
||||
# Escape backslashes first, as they will be as escape symbol in queries
|
||||
# Then escape percent and underscore signs
|
||||
# Finally, replace generic wildcards with sql-style wildcards
|
||||
@@ -79,29 +79,39 @@ itemNameMap = {}
|
||||
def getItem(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(lookfor)
|
||||
item = get_gamedata_session().query(Item).get(lookfor)
|
||||
else:
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in itemNameMap:
|
||||
id = itemNameMap[lookfor]
|
||||
if eager is None:
|
||||
item = gamedata_session.query(Item).get(id)
|
||||
item = get_gamedata_session().query(Item).get(id)
|
||||
else:
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID == id).first()
|
||||
else:
|
||||
# Item names are unique, so we can use first() instead of one()
|
||||
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
||||
item = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
|
||||
if item is not None:
|
||||
itemNameMap[lookfor] = item.ID
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
return item
|
||||
|
||||
@cachedQuery(1, "itemIDs")
|
||||
def getItems(itemIDs, eager=None):
|
||||
if not isinstance(itemIDs, (tuple, list, set)) or not all(isinstance(t, int) for t in itemIDs):
|
||||
raise TypeError("Need iterable of integers as argument")
|
||||
if eager is None:
|
||||
items = get_gamedata_session().query(Item).filter(Item.ID.in_(itemIDs)).all()
|
||||
else:
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).filter(Item.ID.in_(itemIDs)).all()
|
||||
return items
|
||||
|
||||
|
||||
def getMutaplasmid(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -109,7 +119,7 @@ def getMutaplasmid(lookfor, eager=None):
|
||||
|
||||
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||
# A lot of this is described in more detail in #1597
|
||||
item = gamedata_session.query(Item).get(lookfor)
|
||||
item = get_gamedata_session().query(Item).get(lookfor)
|
||||
base = getItem(baseItemID)
|
||||
|
||||
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
|
||||
@@ -125,7 +135,7 @@ def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
|
||||
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
|
||||
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
|
||||
# we want to generate a completely new object to work with
|
||||
gamedata_session.expunge(item)
|
||||
get_gamedata_session().expunge(item)
|
||||
return item
|
||||
|
||||
|
||||
@@ -148,7 +158,7 @@ def getItems(lookfor, eager=None):
|
||||
|
||||
if len(toGet) > 0:
|
||||
# Get items that aren't currently cached, and store them in the cache
|
||||
items = gamedata_session.query(Item).filter(Item.ID.in_(toGet)).all()
|
||||
items = get_gamedata_session().query(Item).filter(Item.ID.in_(toGet)).all()
|
||||
for item in items:
|
||||
cache[(item.ID, None)] = item
|
||||
results += items
|
||||
@@ -162,9 +172,9 @@ def getItems(lookfor, eager=None):
|
||||
def getAlphaClone(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
item = gamedata_session.query(AlphaClone).get(lookfor)
|
||||
item = get_gamedata_session().query(AlphaClone).get(lookfor)
|
||||
else:
|
||||
item = gamedata_session.query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
||||
item = get_gamedata_session().query(AlphaClone).options(*processEager(eager)).filter(AlphaClone.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
return item
|
||||
@@ -172,7 +182,7 @@ def getAlphaClone(lookfor, eager=None):
|
||||
|
||||
def getAlphaCloneList(eager=None):
|
||||
eager = processEager(eager)
|
||||
clones = gamedata_session.query(AlphaClone).options(*eager).all()
|
||||
clones = get_gamedata_session().query(AlphaClone).options(*eager).all()
|
||||
return clones
|
||||
|
||||
|
||||
@@ -183,19 +193,19 @@ groupNameMap = {}
|
||||
def getGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(lookfor)
|
||||
group = get_gamedata_session().query(Group).get(lookfor)
|
||||
else:
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in groupNameMap:
|
||||
id = groupNameMap[lookfor]
|
||||
if eager is None:
|
||||
group = gamedata_session.query(Group).get(id)
|
||||
group = get_gamedata_session().query(Group).get(id)
|
||||
else:
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.ID == id).first()
|
||||
else:
|
||||
# Group names are unique, so we can use first() instead of one()
|
||||
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
||||
group = get_gamedata_session().query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
|
||||
if group is not None:
|
||||
groupNameMap[lookfor] = group.ID
|
||||
else:
|
||||
@@ -210,21 +220,21 @@ categoryNameMap = {}
|
||||
def getCategory(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(lookfor)
|
||||
category = get_gamedata_session().query(Category).get(lookfor)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in categoryNameMap:
|
||||
id = categoryNameMap[lookfor]
|
||||
if eager is None:
|
||||
category = gamedata_session.query(Category).get(id)
|
||||
category = get_gamedata_session().query(Category).get(id)
|
||||
else:
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.ID == id).first()
|
||||
else:
|
||||
# Category names are unique, so we can use first() instead of one()
|
||||
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
|
||||
category = get_gamedata_session().query(Category).options(*processEager(eager)).filter(
|
||||
Category.name == lookfor).first()
|
||||
if category is not None:
|
||||
categoryNameMap[lookfor] = category.ID
|
||||
@@ -240,21 +250,21 @@ metaGroupNameMap = {}
|
||||
def getMetaGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(lookfor)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(lookfor)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == lookfor).first()
|
||||
elif isinstance(lookfor, str):
|
||||
if lookfor in metaGroupNameMap:
|
||||
id = metaGroupNameMap[lookfor]
|
||||
if eager is None:
|
||||
metaGroup = gamedata_session.query(MetaGroup).get(id)
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).get(id)
|
||||
else:
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.ID == id).first()
|
||||
else:
|
||||
# MetaGroup names are unique, so we can use first() instead of one()
|
||||
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
|
||||
metaGroup = get_gamedata_session().query(MetaGroup).options(*processEager(eager)).filter(
|
||||
MetaGroup.name == lookfor).first()
|
||||
if metaGroup is not None:
|
||||
metaGroupNameMap[lookfor] = metaGroup.ID
|
||||
@@ -264,16 +274,16 @@ def getMetaGroup(lookfor, eager=None):
|
||||
|
||||
|
||||
def getMetaGroups():
|
||||
return gamedata_session.query(MetaGroup).all()
|
||||
return get_gamedata_session().query(MetaGroup).all()
|
||||
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getMarketGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
if eager is None:
|
||||
marketGroup = gamedata_session.query(MarketGroup).get(lookfor)
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).get(lookfor)
|
||||
else:
|
||||
marketGroup = gamedata_session.query(MarketGroup).options(*processEager(eager)).filter(
|
||||
marketGroup = get_gamedata_session().query(MarketGroup).options(*processEager(eager)).filter(
|
||||
MarketGroup.ID == lookfor).first()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
@@ -285,7 +295,7 @@ def getMarketTreeNodeIds(rootNodeIds):
|
||||
addedIds = set(rootNodeIds)
|
||||
while addedIds:
|
||||
allIds.update(addedIds)
|
||||
addedIds = {mg.ID for mg in gamedata_session.query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||
addedIds = {mg.ID for mg in get_gamedata_session().query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||
return allIds
|
||||
|
||||
|
||||
@@ -299,7 +309,7 @@ def getItemsByCategory(filter, where=None, eager=None):
|
||||
raise TypeError("Need integer or string as argument")
|
||||
|
||||
filter = processWhere(filter, where)
|
||||
return gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
||||
return get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category).filter(
|
||||
filter).all()
|
||||
|
||||
|
||||
@@ -314,9 +324,9 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
if not hasattr(join, "__iter__"):
|
||||
join = (join,)
|
||||
|
||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(*join)
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||
for token in nameLike.split(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where))
|
||||
else:
|
||||
@@ -325,14 +335,35 @@ def searchItems(nameLike, where=None, join=None, eager=None):
|
||||
return items
|
||||
|
||||
|
||||
@cachedQuery(3, "tokens", "where", "join")
|
||||
def searchItemsRegex(tokens, where=None, join=None, eager=None):
|
||||
if not isinstance(tokens, (tuple, list)) or not all(isinstance(t, str) for t in tokens):
|
||||
raise TypeError("Need tuple or list of strings as argument")
|
||||
|
||||
if join is None:
|
||||
join = tuple()
|
||||
|
||||
if not hasattr(join, "__iter__"):
|
||||
join = (join,)
|
||||
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(*join)
|
||||
for token in tokens:
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.op('regexp')(token), where))
|
||||
else:
|
||||
items = items.filter(Item.name.op('regexp')(token))
|
||||
items = items.limit(100).all()
|
||||
return items
|
||||
|
||||
|
||||
@cachedQuery(3, "where", "nameLike", "join")
|
||||
def searchSkills(nameLike, where=None, eager=None):
|
||||
if not isinstance(nameLike, str):
|
||||
raise TypeError("Need string as argument")
|
||||
|
||||
items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
||||
items = get_gamedata_session().query(Item).options(*processEager(eager)).join(Item.group, Group.category)
|
||||
for token in nameLike.split(' '):
|
||||
token_safe = "%{0}%".format(sqlizeString(token))
|
||||
token_safe = "%{0}%".format(sqlizeNormalString(token))
|
||||
if where is not None:
|
||||
items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where))
|
||||
else:
|
||||
@@ -352,7 +383,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
|
||||
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||
filter = processWhere(itemfilter, where)
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
vars = get_gamedata_session().query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
|
||||
if vars:
|
||||
return vars
|
||||
@@ -360,7 +391,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
itemfilter = or_(*(groups_table.c.groupID == groupID for groupID in groupIDs))
|
||||
filter = processWhere(itemfilter, where)
|
||||
joinon = items_table.c.groupID == groups_table.c.groupID
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
||||
vars = get_gamedata_session().query(Item).options(*processEager(eager)).join((groups_table, joinon)).filter(
|
||||
filter).all()
|
||||
|
||||
return vars
|
||||
@@ -375,7 +406,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
else:
|
||||
raise TypeError("Need integer or string as argument")
|
||||
try:
|
||||
result = gamedata_session.query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
||||
result = get_gamedata_session().query(AttributeInfo).options(*processEager(eager)).filter(filter).one()
|
||||
except exc.NoResultFound:
|
||||
result = None
|
||||
return result
|
||||
@@ -384,7 +415,7 @@ def getAttributeInfo(attr, eager=None):
|
||||
@cachedQuery(1, "field")
|
||||
def getMetaData(field):
|
||||
if isinstance(field, str):
|
||||
data = gamedata_session.query(MetaData).get(field)
|
||||
data = get_gamedata_session().query(MetaData).get(field)
|
||||
else:
|
||||
raise TypeError("Need string as argument")
|
||||
return data
|
||||
@@ -403,12 +434,12 @@ def directAttributeRequest(itemIDs, attrIDs):
|
||||
and_(Attribute.attributeID.in_(attrIDs), Item.typeID.in_(itemIDs)),
|
||||
from_obj=[join(Attribute, Item)])
|
||||
|
||||
result = gamedata_session.execute(q).fetchall()
|
||||
result = get_gamedata_session().execute(q).fetchall()
|
||||
return result
|
||||
|
||||
|
||||
def getAbyssalTypes():
|
||||
return set([r.resultingTypeID for r in gamedata_session.query(DynamicItem.resultingTypeID).distinct()])
|
||||
return set([r.resultingTypeID for r in get_gamedata_session().query(DynamicItem.resultingTypeID).distinct()])
|
||||
|
||||
|
||||
@cachedQuery(1, "itemID")
|
||||
@@ -416,11 +447,17 @@ def getDynamicItem(itemID, eager=None):
|
||||
try:
|
||||
if isinstance(itemID, int):
|
||||
if eager is None:
|
||||
result = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
||||
result = get_gamedata_session().query(DynamicItem).filter(DynamicItem.ID == itemID).one()
|
||||
else:
|
||||
result = gamedata_session.query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
||||
result = get_gamedata_session().query(DynamicItem).options(*processEager(eager)).filter(DynamicItem.ID == itemID).one()
|
||||
else:
|
||||
raise TypeError("Need integer as argument")
|
||||
except exc.NoResultFound:
|
||||
result = None
|
||||
return result
|
||||
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getAllImplantSets():
|
||||
implantSets = get_gamedata_session().query(ImplantSet).all()
|
||||
return implantSets
|
||||
|
||||
@@ -33,9 +33,13 @@ def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_session.execute(commandFits_table.insert(),
|
||||
{"boosterID": value, "boostedID": boosted, "active": 1})
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
pass
|
||||
saveddata_session.commit()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# Shouldn't fail unless you have updated database without the old fleet schema and manually modify the database version
|
||||
# If it does, simply fail. Fleet data migration isn't critically important here
|
||||
|
||||
@@ -4235,6 +4235,8 @@ def upgrade(saveddata_engine):
|
||||
|
||||
# And last but not least, delete the last subsystem
|
||||
saveddata_engine.execute("DELETE FROM modules WHERE ID = ?", (oldModules[4][0],))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
# if something fails, fuck it, we tried. It'll default to the generic conversion below
|
||||
continue
|
||||
|
||||
@@ -6,16 +6,16 @@ Allow use of floats in damage pattern values
|
||||
|
||||
tmpTable = """
|
||||
CREATE TABLE "damagePatternsTemp" (
|
||||
"ID" INTEGER NOT NULL,
|
||||
"name" VARCHAR,
|
||||
"emAmount" FLOAT,
|
||||
"thermalAmount" FLOAT,
|
||||
"kineticAmount" FLOAT,
|
||||
"explosiveAmount" FLOAT,
|
||||
"ownerID" INTEGER,
|
||||
"created" DATETIME,
|
||||
"modified" DATETIME,
|
||||
PRIMARY KEY ("ID"),
|
||||
"ID" INTEGER NOT NULL,
|
||||
"name" VARCHAR,
|
||||
"emAmount" FLOAT,
|
||||
"thermalAmount" FLOAT,
|
||||
"kineticAmount" FLOAT,
|
||||
"explosiveAmount" FLOAT,
|
||||
"ownerID" INTEGER,
|
||||
"created" DATETIME,
|
||||
"modified" DATETIME,
|
||||
PRIMARY KEY ("ID"),
|
||||
FOREIGN KEY("ownerID") REFERENCES users ("ID")
|
||||
)
|
||||
"""
|
||||
|
||||
84
eos/db/migrations/upgrade36.py
Normal file
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Migration 36
|
||||
|
||||
- Shield Booster, Armor Repairer and Capacitor Transfer tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
6441: ( # Small Clarity Ward Enduring Shield Booster
|
||||
6443, # Small Converse Deflection Catalyzer
|
||||
),
|
||||
6437: ( # Small C5-L Compact Shield Booster
|
||||
6439, # Small Neutron Saturation Injector I
|
||||
),
|
||||
10868: ( # Medium Clarity Ward Enduring Shield Booster
|
||||
10870, # Medium Converse Deflection Catalyzer
|
||||
),
|
||||
10872: ( # Medium C5-L Compact Shield Booster
|
||||
10866, # Medium Neutron Saturation Injector I
|
||||
),
|
||||
10876: ( # Large Clarity Ward Enduring Shield Booster
|
||||
10878, # Large Converse Deflection Catalyzer
|
||||
),
|
||||
10880: ( # Large C5-L Compact Shield Booster
|
||||
10874, # Large Neutron Saturation Injector I
|
||||
),
|
||||
10884: ( # X-Large Clarity Ward Enduring Shield Booster
|
||||
10886, # X-Large Converse Deflection Catalyzer
|
||||
),
|
||||
10888: ( # X-Large C5-L Compact Shield Booster
|
||||
10882, # X-Large Neutron Saturation Injector I
|
||||
),
|
||||
4533: ( # Small ACM Compact Armor Repairer
|
||||
4531, # Small Inefficient Armor Repair Unit
|
||||
),
|
||||
4529: ( # Small I-a Enduring Armor Repairer
|
||||
4535, # Small Automated Carapace Restoration
|
||||
),
|
||||
4573: ( # Medium ACM Compact Armor Repairer
|
||||
4571, # Medium Inefficient Armor Repair Unit
|
||||
),
|
||||
4569: ( # Medium I-a Enduring Armor Repairer
|
||||
4575, # Medium Automated Carapace Restoration
|
||||
),
|
||||
22889: ( # 'Meditation' Medium Armor Repairer I
|
||||
4579, # Medium Nano Armor Repair Unit I
|
||||
),
|
||||
4613: ( # Large ACM Compact Armor Repairer
|
||||
4611, # Large Inefficient Armor Repair Unit
|
||||
),
|
||||
4609: ( # Large I-a Enduring Armor Repairer
|
||||
4615, # Large Automated Carapace Restoration
|
||||
),
|
||||
22891: ( # 'Protest' Large Armor Repairer I
|
||||
4621, # Large 'Reprieve' Vestment Reconstructer I
|
||||
),
|
||||
5093: ( # Small Radiative Scoped Remote Capacitor Transmitter
|
||||
5087, # Small Partial E95a Remote Capacitor Transmitter
|
||||
),
|
||||
5091: ( # Small Inductive Compact Remote Capacitor Transmitter
|
||||
5089, # Small Murky Remote Capacitor Transmitter
|
||||
),
|
||||
16489: ( # Medium Radiative Scoped Remote Capacitor Transmitter
|
||||
16493, # Medium Partial E95b Remote Capacitor Transmitter
|
||||
),
|
||||
16495: ( # Medium Inductive Compact Remote Capacitor Transmitter
|
||||
16491, # Medium Murky Remote Capacitor Transmitter
|
||||
),
|
||||
16481: ( # Large Radiative Scoped Remote Capacitor Transmitter
|
||||
16485, # Large Partial E95c Remote Capacitor Transmitter
|
||||
),
|
||||
16487: ( # Large Inductive Compact Remote Capacitor Transmitter
|
||||
16483, # Large Murky Remote Capacitor Transmitter
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
44
eos/db/migrations/upgrade37.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Migration 37
|
||||
|
||||
- Capacitor Booster tiericide
|
||||
"""
|
||||
|
||||
CONVERSIONS = {
|
||||
4959: ( # 'Seed' Micro Capacitor Booster I
|
||||
4957, # Micro Brief Capacitor Overcharge I
|
||||
4961, # Micro Tapered Capacitor Infusion I
|
||||
4955, # Micro F-RX Prototype Capacitor Boost
|
||||
3556, # Micro Capacitor Booster I
|
||||
3558, # Micro Capacitor Booster II
|
||||
15774, # Ammatar Navy Micro Capacitor Booster
|
||||
14180, # Dark Blood Micro Capacitor Booster
|
||||
14182, # True Sansha Micro Capacitor Booster
|
||||
15782, # Imperial Navy Micro Capacitor Booster
|
||||
),
|
||||
5011: ( # Small F-RX Compact Capacitor Booster
|
||||
5009, # Small Brief Capacitor Overcharge I
|
||||
5013, # Small Tapered Capacitor Infusion I
|
||||
5007, # Small F-RX Prototype Capacitor Boost
|
||||
),
|
||||
4833: ( # Medium F-RX Compact Capacitor Booster
|
||||
4831, # Medium Brief Capacitor Overcharge I
|
||||
4835, # Medium Tapered Capacitor Infusion I
|
||||
4829, # Medium F-RX Prototype Capacitor Boost
|
||||
),
|
||||
5051: ( # Heavy F-RX Compact Capacitor Booster
|
||||
5049, # Heavy Brief Capacitor Overcharge I
|
||||
5053, # Heavy Tapered Capacitor Infusion I
|
||||
5047, # Heavy F-RX Prototype Capacitor Boost
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
# Convert modules
|
||||
for replacement_item, list in CONVERSIONS.items():
|
||||
for retired_item in list:
|
||||
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
|
||||
(replacement_item, retired_item))
|
||||
@@ -470,7 +470,7 @@ def searchFits(nameLike, where=None, eager=None):
|
||||
filter = processWhere(Fit.name.like(nameLike, escape="\\"), where)
|
||||
eager = processEager(eager)
|
||||
with sd_lock:
|
||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).all())
|
||||
fits = removeInvalid(saveddata_session.query(Fit).options(*eager).filter(filter).limit(100).all())
|
||||
|
||||
return fits
|
||||
|
||||
@@ -560,6 +560,8 @@ def commit():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.commit()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
@@ -570,6 +572,8 @@ def flush():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.flush()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
293
eos/effects.py
@@ -218,7 +218,7 @@ class Effect48(BaseEffect):
|
||||
powerBooster
|
||||
|
||||
Used by:
|
||||
Modules from group: Capacitor Booster (59 of 59)
|
||||
Modules from group: Capacitor Booster (41 of 41)
|
||||
"""
|
||||
|
||||
type = 'active'
|
||||
@@ -989,7 +989,7 @@ class Effect290(BaseEffect):
|
||||
sharpshooterRangeSkillBonusPostPercentMaxRangeLocationShipModulesRequiringGunnery
|
||||
|
||||
Used by:
|
||||
Implants named like: Frentix Booster (4 of 4)
|
||||
Implants named like: Frentix Booster (9 of 9)
|
||||
Implants named like: Zainou 'Deadeye' Sharpshooter ST (6 of 6)
|
||||
Skill: Sharpshooter
|
||||
"""
|
||||
@@ -1008,7 +1008,7 @@ class Effect298(BaseEffect):
|
||||
surgicalStrikeFalloffBonusPostPercentFalloffLocationShipModulesRequiringGunnery
|
||||
|
||||
Used by:
|
||||
Implants named like: Sooth Sayer Booster (4 of 4)
|
||||
Implants named like: Sooth Sayer Booster (9 of 9)
|
||||
Implants named like: Zainou 'Deadeye' Trajectory Analysis TA (6 of 6)
|
||||
Skill: Trajectory Analysis
|
||||
"""
|
||||
@@ -1742,7 +1742,7 @@ class Effect596(BaseEffect):
|
||||
ammoInfluenceRange
|
||||
|
||||
Used by:
|
||||
Items from category: Charge (590 of 954)
|
||||
Items from category: Charge (590 of 955)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -1757,7 +1757,7 @@ class Effect598(BaseEffect):
|
||||
ammoSpeedMultiplier
|
||||
|
||||
Used by:
|
||||
Charges from group: Festival Charges (28 of 28)
|
||||
Charges from group: Festival Charges (29 of 29)
|
||||
Charges from group: Interdiction Probe (2 of 2)
|
||||
Charges from group: Structure Festival Charges (2 of 2)
|
||||
Special Edition Assetss from group: Festival Charges Expired (4 of 4)
|
||||
@@ -2328,7 +2328,7 @@ class Effect804(BaseEffect):
|
||||
ammoInfluenceCapNeed
|
||||
|
||||
Used by:
|
||||
Items from category: Charge (496 of 954)
|
||||
Items from category: Charge (496 of 955)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -3694,7 +3694,7 @@ class Effect1185(BaseEffect):
|
||||
structureStealthEmitterArraySigDecrease
|
||||
|
||||
Used by:
|
||||
Implants named like: X Instinct Booster (4 of 4)
|
||||
Implants named like: X Instinct Booster (9 of 9)
|
||||
Implants named like: grade Halo (15 of 18)
|
||||
"""
|
||||
|
||||
@@ -5977,6 +5977,7 @@ class Effect2019(BaseEffect):
|
||||
repairDroneShieldBonusBonus
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
|
||||
Modules named like: Drone Repair Augmentor (8 of 8)
|
||||
Skill: Repair Drone Operation
|
||||
"""
|
||||
@@ -5986,7 +5987,7 @@ class Effect2019(BaseEffect):
|
||||
@staticmethod
|
||||
def handler(fit, container, context, projectionRange, **kwargs):
|
||||
level = container.level if 'skill' in context else 1
|
||||
penalized = False if 'skill' in context else True
|
||||
penalized = False if 'skill' in context or 'implant' in context else True
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == 'Logistic Drone',
|
||||
'shieldBonus', container.getModifiedItemAttr('damageHP') * level,
|
||||
stackingPenalties=penalized, **kwargs)
|
||||
@@ -5997,6 +5998,7 @@ class Effect2020(BaseEffect):
|
||||
repairDroneArmorDamageAmountBonus
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
|
||||
Modules named like: Drone Repair Augmentor (8 of 8)
|
||||
Skill: Repair Drone Operation
|
||||
"""
|
||||
@@ -6006,7 +6008,7 @@ class Effect2020(BaseEffect):
|
||||
@staticmethod
|
||||
def handler(fit, container, context, projectionRange, **kwargs):
|
||||
level = container.level if 'skill' in context else 1
|
||||
penalized = False if 'skill' in context else True
|
||||
penalized = False if 'skill' in context or 'implant' in context else True
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == 'Logistic Drone',
|
||||
'armorDamageAmount', container.getModifiedItemAttr('damageHP') * level,
|
||||
stackingPenalties=penalized, **kwargs)
|
||||
@@ -6964,7 +6966,7 @@ class Effect2432(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Implants named like: Inherent Implants 'Squire' Capacitor Management EM (6 of 6)
|
||||
Implants named like: Mindflood Booster (4 of 4)
|
||||
Implants named like: Mindflood Booster (9 of 9)
|
||||
Modules named like: Semiconductor Memory Cell (8 of 8)
|
||||
Implant: Antipharmakon Aeolis
|
||||
Implant: Genolution Core Augmentation CA-1
|
||||
@@ -7148,8 +7150,7 @@ class Effect2491(BaseEffect):
|
||||
def handler(fit, container, context, projectionRange, **kwargs):
|
||||
level = container.level if 'skill' in context else 1
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Burst Jammer',
|
||||
'ecmBurstRange', container.getModifiedItemAttr('rangeSkillBonus') * level,
|
||||
stackingPenalties=False if 'skill' in context else True, **kwargs)
|
||||
'ecmBurstRange', container.getModifiedItemAttr('rangeSkillBonus') * level, **kwargs)
|
||||
|
||||
|
||||
class Effect2492(BaseEffect):
|
||||
@@ -7821,7 +7822,9 @@ class Effect2735(BaseEffect):
|
||||
boosterArmorHpPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Booster (12 of 35)
|
||||
Implants named like: Improved Booster (4 of 8)
|
||||
Implants named like: Standard Booster (4 of 8)
|
||||
Implants named like: Strong Booster (4 of 8)
|
||||
"""
|
||||
|
||||
attr = 'boosterArmorHPPenalty'
|
||||
@@ -7838,9 +7841,15 @@ class Effect2736(BaseEffect):
|
||||
boosterArmorRepairAmountPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Drop Booster (3 of 4)
|
||||
Implants named like: Mindflood Booster (3 of 4)
|
||||
Implants named like: Sooth Sayer Booster (3 of 4)
|
||||
Implant: Improved Drop Booster
|
||||
Implant: Improved Mindflood Booster
|
||||
Implant: Improved Sooth Sayer Booster
|
||||
Implant: Standard Drop Booster
|
||||
Implant: Standard Mindflood Booster
|
||||
Implant: Standard Sooth Sayer Booster
|
||||
Implant: Strong Drop Booster
|
||||
Implant: Strong Mindflood Booster
|
||||
Implant: Strong Sooth Sayer Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterArmorRepairAmountPenalty'
|
||||
@@ -7858,7 +7867,9 @@ class Effect2737(BaseEffect):
|
||||
boosterShieldCapacityPenalty
|
||||
|
||||
Used by:
|
||||
Implants from group: Booster (12 of 67)
|
||||
Implants named like: Improved Booster (4 of 8)
|
||||
Implants named like: Standard Booster (4 of 8)
|
||||
Implants named like: Strong Booster (4 of 8)
|
||||
"""
|
||||
|
||||
attr = 'boosterShieldCapacityPenalty'
|
||||
@@ -7875,9 +7886,15 @@ class Effect2739(BaseEffect):
|
||||
boosterTurretOptimalRangePenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Blue Pill Booster (3 of 5)
|
||||
Implants named like: Mindflood Booster (3 of 4)
|
||||
Implants named like: Sooth Sayer Booster (3 of 4)
|
||||
Implant: Improved Blue Pill Booster
|
||||
Implant: Improved Mindflood Booster
|
||||
Implant: Improved Sooth Sayer Booster
|
||||
Implant: Standard Blue Pill Booster
|
||||
Implant: Standard Mindflood Booster
|
||||
Implant: Standard Sooth Sayer Booster
|
||||
Implant: Strong Blue Pill Booster
|
||||
Implant: Strong Mindflood Booster
|
||||
Implant: Strong Sooth Sayer Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterTurretOptimalRangePenalty'
|
||||
@@ -7895,8 +7912,12 @@ class Effect2741(BaseEffect):
|
||||
boosterTurretFalloffPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Drop Booster (3 of 4)
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
Implant: Improved Drop Booster
|
||||
Implant: Improved X-Instinct Booster
|
||||
Implant: Standard Drop Booster
|
||||
Implant: Standard X-Instinct Booster
|
||||
Implant: Strong Drop Booster
|
||||
Implant: Strong X-Instinct Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterTurretFalloffPenalty'
|
||||
@@ -7914,8 +7935,12 @@ class Effect2745(BaseEffect):
|
||||
boosterCapacitorCapacityPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Blue Pill Booster (3 of 5)
|
||||
Implants named like: Exile Booster (3 of 4)
|
||||
Implant: Improved Blue Pill Booster
|
||||
Implant: Improved Exile Booster
|
||||
Implant: Standard Blue Pill Booster
|
||||
Implant: Standard Exile Booster
|
||||
Implant: Strong Blue Pill Booster
|
||||
Implant: Strong Exile Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterCapacitorCapacityPenalty'
|
||||
@@ -7932,8 +7957,10 @@ class Effect2746(BaseEffect):
|
||||
boosterMaxVelocityPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Crash Booster (3 of 4)
|
||||
Items from market group: Implants & Boosters > Booster > Booster Slot 02 (9 of 13)
|
||||
Implant: Improved Crash Booster
|
||||
Implant: Standard Crash Booster
|
||||
Implant: Strong Crash Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterMaxVelocityPenalty'
|
||||
@@ -7950,8 +7977,12 @@ class Effect2747(BaseEffect):
|
||||
boosterTurretTrackingPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Exile Booster (3 of 4)
|
||||
Implants named like: Frentix Booster (3 of 4)
|
||||
Implant: Improved Exile Booster
|
||||
Implant: Improved Frentix Booster
|
||||
Implant: Standard Exile Booster
|
||||
Implant: Standard Frentix Booster
|
||||
Implant: Strong Exile Booster
|
||||
Implant: Strong Frentix Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterTurretTrackingPenalty'
|
||||
@@ -7969,8 +8000,12 @@ class Effect2748(BaseEffect):
|
||||
boosterMissileVelocityPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Crash Booster (3 of 4)
|
||||
Implants named like: X Instinct Booster (3 of 4)
|
||||
Implant: Improved Crash Booster
|
||||
Implant: Improved X-Instinct Booster
|
||||
Implant: Standard Crash Booster
|
||||
Implant: Standard X-Instinct Booster
|
||||
Implant: Strong Crash Booster
|
||||
Implant: Strong X-Instinct Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterMissileVelocityPenalty'
|
||||
@@ -7988,7 +8023,9 @@ class Effect2749(BaseEffect):
|
||||
boosterMissileExplosionVelocityPenalty
|
||||
|
||||
Used by:
|
||||
Implants named like: Blue Pill Booster (3 of 5)
|
||||
Implant: Improved Blue Pill Booster
|
||||
Implant: Standard Blue Pill Booster
|
||||
Implant: Strong Blue Pill Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterAOEVelocityPenalty'
|
||||
@@ -8143,8 +8180,12 @@ class Effect2791(BaseEffect):
|
||||
boosterMissileExplosionCloudPenaltyFixed
|
||||
|
||||
Used by:
|
||||
Implants named like: Exile Booster (3 of 4)
|
||||
Implants named like: Mindflood Booster (3 of 4)
|
||||
Implant: Improved Exile Booster
|
||||
Implant: Improved Mindflood Booster
|
||||
Implant: Standard Exile Booster
|
||||
Implant: Standard Mindflood Booster
|
||||
Implant: Strong Exile Booster
|
||||
Implant: Strong Mindflood Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterMissileAOECloudPenalty'
|
||||
@@ -8436,7 +8477,7 @@ class Effect2847(BaseEffect):
|
||||
trackingSpeedBonusPassiveRequiringGunneryTrackingSpeedBonus
|
||||
|
||||
Used by:
|
||||
Implants named like: Drop Booster (4 of 4)
|
||||
Implants named like: Drop Booster (9 of 9)
|
||||
Implants named like: Eifyr and Co. 'Gunslinger' Motion Prediction MR (6 of 6)
|
||||
Implant: Antipharmakon Iokira
|
||||
Implant: Ogdin's Eye Coordination Enhancer
|
||||
@@ -9189,12 +9230,12 @@ class Effect3002(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||
Modules from group: Capacitor Booster (59 of 59)
|
||||
Modules from group: Capacitor Booster (41 of 41)
|
||||
Modules from group: Energy Neutralizer (54 of 54)
|
||||
Modules from group: Energy Nosferatu (54 of 54)
|
||||
Modules from group: Hull Repair Unit (25 of 25)
|
||||
Modules from group: Remote Armor Repairer (39 of 39)
|
||||
Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
Modules from group: Remote Capacitor Transmitter (35 of 35)
|
||||
Modules from group: Remote Shield Booster (38 of 38)
|
||||
Modules from group: Smart Bomb (118 of 118)
|
||||
Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||
@@ -9303,9 +9344,9 @@ class Effect3029(BaseEffect):
|
||||
overloadSelfEmHardeningBonus
|
||||
|
||||
Used by:
|
||||
Modules named like: Anti EM Shield Hardener (21 of 21)
|
||||
Variations of module: Anti-EM Shield Hardener I (20 of 20)
|
||||
Variations of module: Armor EM Hardener I (39 of 39)
|
||||
Variations of module: EM Ward Field I (19 of 19)
|
||||
Module: Civilian EM Ward Field
|
||||
"""
|
||||
|
||||
type = 'overheat'
|
||||
@@ -9320,9 +9361,9 @@ class Effect3030(BaseEffect):
|
||||
overloadSelfThermalHardeningBonus
|
||||
|
||||
Used by:
|
||||
Variations of module: Anti-Thermal Shield Hardener I (20 of 20)
|
||||
Variations of module: Armor Thermal Hardener I (39 of 39)
|
||||
Variations of module: Thermal Dissipation Field I (19 of 19)
|
||||
Module: Civilian Thermal Dissipation Field
|
||||
Module: Civilian Anti-Thermal Shield Hardener
|
||||
"""
|
||||
|
||||
type = 'overheat'
|
||||
@@ -9337,9 +9378,9 @@ class Effect3031(BaseEffect):
|
||||
overloadSelfExplosiveHardeningBonus
|
||||
|
||||
Used by:
|
||||
Variations of module: Anti-Explosive Shield Hardener I (20 of 20)
|
||||
Variations of module: Armor Explosive Hardener I (39 of 39)
|
||||
Variations of module: Explosive Deflection Field I (19 of 19)
|
||||
Module: Civilian Explosive Deflection Field
|
||||
Module: Civilian Anti-Explosive Shield Hardener
|
||||
"""
|
||||
|
||||
type = 'overheat'
|
||||
@@ -9354,9 +9395,9 @@ class Effect3032(BaseEffect):
|
||||
overloadSelfKineticHardeningBonus
|
||||
|
||||
Used by:
|
||||
Modules named like: Anti Kinetic Shield Hardener (21 of 21)
|
||||
Variations of module: Anti-Kinetic Shield Hardener I (20 of 20)
|
||||
Variations of module: Armor Kinetic Hardener I (39 of 39)
|
||||
Variations of module: Kinetic Deflection Field I (19 of 19)
|
||||
Module: Civilian Kinetic Deflection Field
|
||||
"""
|
||||
|
||||
type = 'overheat'
|
||||
@@ -9372,7 +9413,7 @@ class Effect3035(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Modules named like: Capital Flex Hardener (9 of 9)
|
||||
Variations of module: Adaptive Invulnerability Field I (17 of 17)
|
||||
Variations of module: Adaptive Invulnerability Shield Hardener I (18 of 18)
|
||||
"""
|
||||
|
||||
type = 'overheat'
|
||||
@@ -16714,7 +16755,7 @@ class Effect4951(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Implants named like: Agency 'Hardshell' TB Dose (4 of 4)
|
||||
Implants named like: Blue Pill Booster (5 of 5)
|
||||
Implants named like: Blue Pill Booster (10 of 10)
|
||||
Implant: Antipharmakon Thureo
|
||||
"""
|
||||
|
||||
@@ -16768,9 +16809,15 @@ class Effect4970(BaseEffect):
|
||||
boosterShieldBoostAmountPenaltyShieldSkills
|
||||
|
||||
Used by:
|
||||
Implants named like: Crash Booster (3 of 4)
|
||||
Implants named like: Frentix Booster (3 of 4)
|
||||
Implants named like: Mindflood Booster (3 of 4)
|
||||
Implant: Improved Crash Booster
|
||||
Implant: Improved Frentix Booster
|
||||
Implant: Improved Mindflood Booster
|
||||
Implant: Standard Crash Booster
|
||||
Implant: Standard Frentix Booster
|
||||
Implant: Standard Mindflood Booster
|
||||
Implant: Strong Crash Booster
|
||||
Implant: Strong Frentix Booster
|
||||
Implant: Strong Mindflood Booster
|
||||
"""
|
||||
|
||||
attr = 'boosterShieldBoostAmountPenalty'
|
||||
@@ -16872,7 +16919,7 @@ class Effect4989(BaseEffect):
|
||||
missileSkillAoeCloudSizeBonusAllIncludingCapitals
|
||||
|
||||
Used by:
|
||||
Implants named like: Crash Booster (4 of 4)
|
||||
Implants named like: Crash Booster (9 of 9)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -18631,7 +18678,7 @@ class Effect5230(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Modules from group: Flex Shield Hardener (5 of 5)
|
||||
Modules from group: Shield Hardener (97 of 97)
|
||||
Modules from group: Shield Hardener (102 of 102)
|
||||
"""
|
||||
|
||||
type = 'active'
|
||||
@@ -19797,7 +19844,7 @@ class Effect5364(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Implants named like: Agency 'Hardshell' TB Dose (4 of 4)
|
||||
Implants named like: Exile Booster (4 of 4)
|
||||
Implants named like: Exile Booster (9 of 9)
|
||||
Implant: Antipharmakon Kosybo
|
||||
"""
|
||||
|
||||
@@ -22035,6 +22082,7 @@ class Effect5769(BaseEffect):
|
||||
repairDroneHullBonusBonus
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Repair Drone Operation DR (3 of 3)
|
||||
Modules named like: Drone Repair Augmentor (8 of 8)
|
||||
Skill: Repair Drone Operation
|
||||
"""
|
||||
@@ -25036,7 +25084,7 @@ class Effect6184(BaseEffect):
|
||||
shipModuleRemoteCapacitorTransmitter
|
||||
|
||||
Used by:
|
||||
Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
Modules from group: Remote Capacitor Transmitter (35 of 35)
|
||||
"""
|
||||
|
||||
runTime = 'late'
|
||||
@@ -29064,10 +29112,6 @@ class Effect6582(BaseEffect):
|
||||
mod.item.requiresSkill('Capital Precursor Weapon'),
|
||||
'damageMultiplier', src.getModifiedItemAttr('siegeTurretDamageBonus'), **kwargs)
|
||||
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill('Motion Prediction'),
|
||||
'damageMultiplier', src.getModifiedItemAttr('siegeHAWTurretDamageBonus'),
|
||||
stackingPenalties=True, **kwargs)
|
||||
|
||||
# Missiles
|
||||
for type in ('kinetic', 'thermal', 'explosive', 'em'):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('XL Torpedoes') or
|
||||
@@ -29079,7 +29123,7 @@ class Effect6582(BaseEffect):
|
||||
mod.item.requiresSkill('XL Cruise Missiles'),
|
||||
'speed', src.getModifiedItemAttr('siegeLauncherROFBonus'), **kwargs)
|
||||
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Target Navigation Prediction'),
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Missile Launcher Rapid Torpedo',
|
||||
'speed', src.getModifiedItemAttr('siegeHAWMissileROFBonus'),
|
||||
stackingPenalties=True, penaltyGroup='postPercent', **kwargs)
|
||||
|
||||
@@ -30461,6 +30505,7 @@ class Effect6664(BaseEffect):
|
||||
skillBonusDroneSharpshooting
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Drone Sharpshooting DS (3 of 3)
|
||||
Skill: Drone Sharpshooting
|
||||
"""
|
||||
|
||||
@@ -30469,16 +30514,18 @@ class Effect6664(BaseEffect):
|
||||
@staticmethod
|
||||
def handler(fit, src, context, projectionRange, **kwargs):
|
||||
lvl = src.level if 'skill' in context else 1
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill('Drones'), 'maxRange',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityMissilesRange',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'),
|
||||
'fighterAbilityAttackTurretRangeOptimal',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(lambda mod: mod.item.requiresSkill('Fighters'),
|
||||
'fighterAbilityAttackMissileRangeOptimal',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Drones'), 'maxRange',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityMissilesRange',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityAttackTurretRangeOptimal',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
fit.fighters.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill('Fighters'), 'fighterAbilityAttackMissileRangeOptimal',
|
||||
src.getModifiedItemAttr('rangeSkillBonus') * lvl, **kwargs)
|
||||
|
||||
|
||||
class Effect6665(BaseEffect):
|
||||
@@ -30486,6 +30533,7 @@ class Effect6665(BaseEffect):
|
||||
skillBonusDroneDurability
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Drone Durability DD (3 of 3)
|
||||
Skill: Drone Durability
|
||||
"""
|
||||
|
||||
@@ -30509,6 +30557,7 @@ class Effect6667(BaseEffect):
|
||||
skillBonusDroneNavigation
|
||||
|
||||
Used by:
|
||||
Implants named like: Black Market 'Valdimar' Drone Navigation DN (3 of 3)
|
||||
Skill: Drone Navigation
|
||||
"""
|
||||
|
||||
@@ -32660,6 +32709,8 @@ class Effect6871(BaseEffect):
|
||||
# via https://forums.eveonline.com/default.aspx?g=posts&t=515826
|
||||
try:
|
||||
bonus = max(0, min(50.0, (src.owner.character.secStatus * 10)))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
bonus = None
|
||||
|
||||
@@ -35662,7 +35713,7 @@ class Effect7177(BaseEffect):
|
||||
skillBonusDroneDurabilityNotFighters
|
||||
|
||||
Used by:
|
||||
Implants from group: Cyber Drones (4 of 4)
|
||||
Implants named like: Drone Tuner (4 of 4)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -36256,3 +36307,109 @@ class Effect8018(BaseEffect):
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Shield Emission Systems') or mod.item.requiresSkill('Remote Armor Repair Systems'),
|
||||
'duration', implant.getModifiedItemAttr('remoteRepDurationBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8021(BaseEffect):
|
||||
"""
|
||||
hydraSetBonus
|
||||
|
||||
Used by:
|
||||
Implants named like: grade Hydra (18 of 18)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
for attr in ('hydraDroneTrackingBonus', 'hydraDroneRangeBonus', 'hydraMissileFlightTimeBonus', 'hydraMissileExplosionVelocityBonus'):
|
||||
fit.appliedImplants.filteredItemMultiply(
|
||||
lambda implant: implant.item.requiresSkill('Cybernetics'),
|
||||
attr, implant.getModifiedItemAttr('implantSetHydra'), **kwargs)
|
||||
|
||||
|
||||
class Effect8023(BaseEffect):
|
||||
"""
|
||||
hydraDroneTrackingEffect
|
||||
|
||||
Used by:
|
||||
Implants named like: grade Hydra (15 of 18)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'trackingSpeed', implant.getModifiedItemAttr('hydraDroneTrackingBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8024(BaseEffect):
|
||||
"""
|
||||
hydraDroneRangeEffect
|
||||
|
||||
Used by:
|
||||
Implants named like: grade Hydra (15 of 18)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
for attr in ('maxRange', 'falloff'):
|
||||
fit.drones.filteredItemBoost(
|
||||
lambda drone: drone.item.requiresSkill('Drones'),
|
||||
attr, implant.getModifiedItemAttr('hydraDroneRangeBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8025(BaseEffect):
|
||||
"""
|
||||
hydraMissileFlightTimeEffect
|
||||
|
||||
Used by:
|
||||
Implants named like: grade Hydra (15 of 18)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
|
||||
'explosionDelay', implant.getModifiedItemAttr('hydraMissileFlightTimeBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8026(BaseEffect):
|
||||
"""
|
||||
hydraMissileExplosionVelocityEffect
|
||||
|
||||
Used by:
|
||||
Implants named like: grade Hydra (15 of 18)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, implant, context, projectionRange, **kwargs):
|
||||
fit.modules.filteredChargeBoost(
|
||||
lambda mod: mod.charge.requiresSkill('Missile Launcher Operation'),
|
||||
'aoeVelocity', implant.getModifiedItemAttr('hydraMissileExplosionVelocityBonus'), **kwargs)
|
||||
|
||||
|
||||
class Effect8029(BaseEffect):
|
||||
"""
|
||||
roleBonus7CapBoosterGroupRestriction
|
||||
|
||||
Used by:
|
||||
Ships from group: Force Auxiliary (6 of 6)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context, projectionRange, **kwargs):
|
||||
for attr in ('maxGroupOnline', 'maxGroupFitted'):
|
||||
fit.modules.filteredItemForce(
|
||||
lambda mod: mod.item.group.name == 'Capacitor Booster',
|
||||
attr, ship.getModifiedItemAttr('shipBonusRole7'), **kwargs)
|
||||
|
||||
@@ -184,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"
|
||||
@@ -200,45 +202,20 @@ class Effect(EqBase):
|
||||
|
||||
try:
|
||||
return self.__effectDef.get(key, None)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return getattr(self.__effectDef, key, None)
|
||||
|
||||
|
||||
class Item(EqBase):
|
||||
MOVE_ATTRS = (4, # Mass
|
||||
38, # Capacity
|
||||
161) # Volume
|
||||
|
||||
MOVE_ATTR_INFO = None
|
||||
|
||||
ABYSSAL_TYPES = None
|
||||
|
||||
@classmethod
|
||||
def getMoveAttrInfo(cls):
|
||||
info = getattr(cls, "MOVE_ATTR_INFO", None)
|
||||
if info is None:
|
||||
cls.MOVE_ATTR_INFO = info = []
|
||||
for id in cls.MOVE_ATTRS:
|
||||
info.append(eos.db.getAttributeInfo(id))
|
||||
|
||||
return info
|
||||
|
||||
def moveAttrs(self):
|
||||
self.__moved = True
|
||||
for info in self.getMoveAttrInfo():
|
||||
val = getattr(self, info.name, 0)
|
||||
if val != 0:
|
||||
attr = Attribute()
|
||||
attr.info = info
|
||||
attr.value = val
|
||||
self.__attributes[info.name] = attr
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__race = None
|
||||
self.__requiredSkills = None
|
||||
self.__requiredFor = None
|
||||
self.__moved = False
|
||||
self.__offensive = None
|
||||
self.__assistive = None
|
||||
self.__overrides = None
|
||||
@@ -260,9 +237,6 @@ class Item(EqBase):
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
if not self.__moved:
|
||||
self.moveAttrs()
|
||||
|
||||
return self.__attributes
|
||||
|
||||
@property
|
||||
@@ -719,5 +693,15 @@ class Unit(EqBase):
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class Traits(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class ImplantSet(EqBase):
|
||||
|
||||
@property
|
||||
def fullName(self):
|
||||
if not self.gradeName:
|
||||
return self.setName
|
||||
return '{} {}'.format(self.gradeName, self.setName)
|
||||
|
||||
@@ -21,6 +21,9 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.round import roundToPrec
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@@ -56,9 +59,8 @@ class BoosterSideEffect:
|
||||
@property
|
||||
def name(self):
|
||||
return "{0}% {1}".format(
|
||||
self.booster.getModifiedItemAttr(self.attr),
|
||||
self.__effect.getattr('displayName') or self.__effect.name,
|
||||
)
|
||||
roundToPrec(self.booster.getModifiedItemAttr(self.attr), 5),
|
||||
self.__effect.getattr('displayName') or self.__effect.name)
|
||||
|
||||
@property
|
||||
def attr(self):
|
||||
|
||||
@@ -260,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
|
||||
@@ -274,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
|
||||
|
||||
|
||||
@@ -576,11 +576,15 @@ class Fit:
|
||||
|
||||
if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
||||
"Shield Emission Systems"), "capacitorNeed", value)
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||
mod.item.requiresSkill("Shield Emission Systems") or
|
||||
mod.item.requiresSkill("Capital Shield Emission Systems"),
|
||||
"capacitorNeed", value)
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill(
|
||||
"Shield Emission Systems"), "duration", value)
|
||||
lambda mod: mod.item.requiresSkill("Shield Operation") or
|
||||
mod.item.requiresSkill("Shield Emission Systems") or
|
||||
mod.item.requiresSkill("Capital Shield Emission Systems"),
|
||||
"duration", value)
|
||||
|
||||
if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP
|
||||
self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True)
|
||||
@@ -590,12 +594,16 @@ class Fit:
|
||||
self.ship.boostItemAttr("armor%sDamageResonance" % damageType, value, stackingPenalties=True)
|
||||
|
||||
if warfareBuffID == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems"),
|
||||
"capacitorNeed", value)
|
||||
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems"),
|
||||
"duration", value)
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems") or
|
||||
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||
"capacitorNeed", value)
|
||||
self.modules.filteredItemBoost(
|
||||
lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems") or
|
||||
mod.item.requiresSkill("Repair Systems") or
|
||||
mod.item.requiresSkill("Capital Remote Armor Repair Systems"),
|
||||
"duration", value)
|
||||
|
||||
if warfareBuffID == 15: # Armor Burst: Armor Reinforcement: Armor HP
|
||||
self.ship.boostItemAttr("armorHP", value, stackingPenalties=True)
|
||||
@@ -1018,6 +1026,16 @@ class Fit:
|
||||
if mod.isEmpty:
|
||||
del self.modules[i]
|
||||
|
||||
def clearTail(self):
|
||||
tailPositions = {}
|
||||
for mod in self.modules:
|
||||
if not mod.isEmpty:
|
||||
break
|
||||
tailPositions[self.modules.index(mod)] = mod.slot
|
||||
for pos in sorted(tailPositions, reverse=True):
|
||||
self.modules.remove(self.modules[pos])
|
||||
return tailPositions
|
||||
|
||||
@property
|
||||
def modCount(self):
|
||||
x = 0
|
||||
@@ -1127,7 +1145,7 @@ class Fit:
|
||||
def droneBayUsed(self):
|
||||
amount = 0
|
||||
for d in self.drones:
|
||||
amount += d.item.volume * d.amount
|
||||
amount += d.item.attributes['volume'].value * d.amount
|
||||
|
||||
return amount
|
||||
|
||||
@@ -1135,7 +1153,7 @@ class Fit:
|
||||
def fighterBayUsed(self):
|
||||
amount = 0
|
||||
for f in self.fighters:
|
||||
amount += f.item.volume * f.amount
|
||||
amount += f.item.attributes['volume'].value * f.amount
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
@@ -214,8 +214,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if charge is None:
|
||||
charges = 0
|
||||
else:
|
||||
chargeVolume = charge.volume
|
||||
containerCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
containerCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
else:
|
||||
@@ -696,7 +696,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
# Check this only if we're told to do so
|
||||
if hardpointLimit:
|
||||
if fit.getHardpointsFree(self.hardpoint) < 1:
|
||||
if fit.getHardpointsFree(self.hardpoint) < (1 if self.owner != fit else 0):
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -778,8 +778,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# Check sizes, if 'charge size > module volume' it won't fit
|
||||
if charge is None:
|
||||
return True
|
||||
chargeVolume = charge.volume
|
||||
moduleCapacity = self.item.capacity
|
||||
chargeVolume = charge.attributes['volume'].value
|
||||
moduleCapacity = self.item.attributes['capacity'].value
|
||||
if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
|
||||
return False
|
||||
|
||||
|
||||
@@ -73,12 +73,16 @@ class Mutator(EqBase):
|
||||
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
|
||||
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
|
||||
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
self.module = None
|
||||
|
||||
@validates("value")
|
||||
def validator(self, key, val):
|
||||
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
|
||||
if self.baseValue == 0:
|
||||
return 0
|
||||
mod = val / self.baseValue
|
||||
|
||||
if self.minMod <= mod <= self.maxMod:
|
||||
|
||||
@@ -294,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
|
||||
@@ -312,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
|
||||
|
||||
27
eos/utils/round.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import math
|
||||
|
||||
|
||||
def roundToPrec(val, prec, nsValue=None):
|
||||
"""
|
||||
nsValue: custom value which should be used to determine normalization shift
|
||||
"""
|
||||
# We're not rounding integers anyway
|
||||
# Also make sure that we do not ask to calculate logarithm of zero
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||
# But we don't want to round integers
|
||||
if roundFactor < 0:
|
||||
roundFactor = 0
|
||||
# Do actual rounding
|
||||
val = round(val, roundFactor)
|
||||
# Make sure numbers with .0 part designating float don't get through
|
||||
if int(val) == val:
|
||||
val = int(val)
|
||||
return val
|
||||
|
||||
|
||||
def roundDec(val, prec):
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
return round(val, prec)
|
||||
@@ -26,11 +26,12 @@ VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle'
|
||||
|
||||
class YDef:
|
||||
|
||||
def __init__(self, handle, unit, label, selectorLabel=None):
|
||||
def __init__(self, handle, unit, label, selectorLabel=None, hidden=False):
|
||||
self.handle = handle
|
||||
self.unit = unit
|
||||
self.label = label
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
@@ -53,12 +54,13 @@ class YDef:
|
||||
|
||||
class XDef:
|
||||
|
||||
def __init__(self, handle, unit, label, mainInput, selectorLabel=None):
|
||||
def __init__(self, handle, unit, label, mainInput, selectorLabel=None, hidden=False):
|
||||
self.handle = handle
|
||||
self.unit = unit
|
||||
self.label = label
|
||||
self.mainInput = mainInput
|
||||
self._selectorLabel = selectorLabel
|
||||
self.hidden = hidden
|
||||
|
||||
@property
|
||||
def selectorLabel(self):
|
||||
|
||||
@@ -31,8 +31,8 @@ from .getter import (
|
||||
|
||||
class FitDamageStatsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
self._projectedCache = ProjectedDataCache()
|
||||
|
||||
|
||||
@@ -23,24 +23,6 @@ import math
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
class Time2SpeedGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
return {
|
||||
'maxSpeed': src.getMaxVelocity(),
|
||||
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
||||
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
time = x
|
||||
maxSpeed = commonData['maxSpeed']
|
||||
mass = commonData['mass']
|
||||
agility = commonData['agility']
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||
return speed
|
||||
|
||||
|
||||
class Time2DistanceGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
@@ -60,3 +42,62 @@ class Time2DistanceGetter(SmoothPointGetter):
|
||||
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
|
||||
distance = distance_t - distance_0
|
||||
return distance
|
||||
|
||||
|
||||
class Time2SpeedGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
return {
|
||||
'maxSpeed': src.getMaxVelocity(),
|
||||
'mass': src.item.ship.getModifiedItemAttr('mass'),
|
||||
'agility': src.item.ship.getModifiedItemAttr('agility')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
time = x
|
||||
maxSpeed = commonData['maxSpeed']
|
||||
mass = commonData['mass']
|
||||
agility = commonData['agility']
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||
return speed
|
||||
|
||||
|
||||
class Time2MomentumGetter(Time2SpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
mass = commonData['mass']
|
||||
speed = Time2SpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
momentum = speed * mass
|
||||
return momentum
|
||||
|
||||
|
||||
class Time2BumpSpeedGetter(Time2SpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||
bumperMass = commonData['mass'] / 10 ** 6
|
||||
bumperSpeed = Time2SpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||
tgtSpeed = (2 * bumperSpeed * bumperMass) / (bumperMass + tgtMass)
|
||||
return tgtSpeed
|
||||
|
||||
|
||||
class Time2BumpDistanceGetter(Time2BumpSpeedGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
# S. Santorine, Ship Motion in EVE-Online, p3, Collisions & Bumping section
|
||||
# https://docs.google.com/document/d/1rwVWjTvzVdPEFETf0vwm649AFb4bgRBaNLpRPaoB03o
|
||||
# Internally, Santorine's formulas are using millions of kilograms, so we normalize to them here
|
||||
tgtMass = miscParams['tgtMass'] / 10 ** 6
|
||||
tgtInertia = miscParams['tgtInertia']
|
||||
tgtSpeed = Time2BumpSpeedGetter._calculatePoint(
|
||||
self, x=x, miscParams=miscParams,
|
||||
src=src, tgt=tgt, commonData=commonData)
|
||||
tgtDistance = tgtSpeed * tgtMass * tgtInertia
|
||||
return tgtDistance
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
|
||||
from graphs.data.base import FitGraph, XDef, YDef, Input
|
||||
from .getter import Time2SpeedGetter, Time2DistanceGetter
|
||||
from .getter import Time2SpeedGetter, Time2DistanceGetter, Time2MomentumGetter, Time2BumpSpeedGetter, Time2BumpDistanceGetter
|
||||
|
||||
|
||||
class FitMobilityGraph(FitGraph):
|
||||
@@ -30,12 +30,26 @@ class FitMobilityGraph(FitGraph):
|
||||
xDefs = [XDef(handle='time', unit='s', label='Time', mainInput=('time', 's'))]
|
||||
yDefs = [
|
||||
YDef(handle='speed', unit='m/s', label='Speed'),
|
||||
YDef(handle='distance', unit='km', label='Distance')]
|
||||
inputs = [Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30))]
|
||||
YDef(handle='distance', unit='km', label='Distance'),
|
||||
YDef(handle='momentum', unit='Gkg⋅m/s', label='Momentum'),
|
||||
YDef(handle='bumpSpeed', unit='m/s', label='Target speed', selectorLabel='Bump speed'),
|
||||
YDef(handle='bumpDistance', unit='km', label='Target distance traveled', selectorLabel='Bump distance')]
|
||||
inputs = [
|
||||
Input(handle='time', unit='s', label='Time', iconID=1392, defaultValue=10, defaultRange=(0, 30)),
|
||||
# Default values in target fields correspond to a random carrier/fax
|
||||
Input(handle='tgtMass', unit='Mkg', label='Target mass', iconID=76, defaultValue=1300, defaultRange=(100, 2500), conditions=[(None, ('bumpSpeed', 'm/s')), (None, ('bumpDistance', 'km'))], secondaryTooltip='Defined in millions of kilograms'),
|
||||
Input(handle='tgtInertia', unit=None, label='Target inertia factor', iconID=1401, defaultValue=0.015, defaultRange=(0.03, 0.1), conditions=[(None, ('bumpDistance', 'km'))], secondaryTooltip='Inertia Modifier attribute value of the target ship')]
|
||||
srcExtraCols = ('Speed', 'Agility')
|
||||
|
||||
# Calculation stuff
|
||||
_normalizers = {('tgtMass', 'Mkg'): lambda v, src, tgt: None if v is None else v * 10 ** 6}
|
||||
_getters = {
|
||||
('time', 'speed'): Time2SpeedGetter,
|
||||
('time', 'distance'): Time2DistanceGetter}
|
||||
_denormalizers = {('distance', 'km'): lambda v, src, tgt: v / 1000}
|
||||
('time', 'distance'): Time2DistanceGetter,
|
||||
('time', 'momentum'): Time2MomentumGetter,
|
||||
('time', 'bumpSpeed'): Time2BumpSpeedGetter,
|
||||
('time', 'bumpDistance'): Time2BumpDistanceGetter}
|
||||
_denormalizers = {
|
||||
('distance', 'km'): lambda v, src, tgt: v / 1000,
|
||||
('momentum', 'Gkg⋅m/s'): lambda v, src, tgt: v / 10 ** 9,
|
||||
('bumpDistance', 'km'): lambda v, src, tgt: v / 1000}
|
||||
|
||||
@@ -26,8 +26,8 @@ from .getter import Distance2RpsGetter, Distance2RepAmountGetter, Time2RpsGetter
|
||||
|
||||
class FitRemoteRepsGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._timeCache = TimeCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -27,6 +27,10 @@ from .getter import (
|
||||
|
||||
class FitShieldRegenGraph(FitGraph):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||
|
||||
# UI stuff
|
||||
internalName = 'shieldRegenGraph'
|
||||
name = 'Shield Regeneration'
|
||||
@@ -73,7 +77,3 @@ class FitShieldRegenGraph(FitGraph):
|
||||
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
|
||||
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
|
||||
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.isEffective = gui.mainFrame.MainFrame.getInstance().statsPane.nameViewMap['resistancesViewFull'].showEffective
|
||||
|
||||
@@ -26,8 +26,8 @@ from .getter import AU_METERS, Distance2TimeGetter
|
||||
|
||||
class FitWarpTimeGraph(FitGraph):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._subspeedCache = SubwarpSpeedCache()
|
||||
|
||||
def _clearInternalCache(self, reason, extraData):
|
||||
|
||||
@@ -54,6 +54,8 @@ try:
|
||||
except ImportError as e:
|
||||
pyfalog.warning('Matplotlib failed to import. Likely missing or incompatible version.')
|
||||
graphFrame_enabled = False
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
# We can get exceptions deep within matplotlib. Catch those. See GH #1046
|
||||
tb = traceback.format_exc()
|
||||
@@ -71,6 +73,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
# Remove matplotlib font cache, see #234
|
||||
try:
|
||||
cache_dir = mpl._get_cachedir()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
cache_dir = os.path.expanduser(os.path.join('~', '.matplotlib'))
|
||||
cache_file = os.path.join(cache_dir, 'fontList.cache')
|
||||
@@ -168,6 +172,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
legendData.append((color, lineStyle, source.shortName))
|
||||
else:
|
||||
legendData.append((color, lineStyle, '{} vs {}'.format(source.shortName, target.shortName)))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
pyfalog.warning('Failed to plot "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
||||
self.canvas.draw()
|
||||
@@ -241,6 +247,8 @@ class GraphCanvasPanel(wx.Panel):
|
||||
src=source,
|
||||
tgt=target)
|
||||
addYMark(y)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception:
|
||||
pyfalog.warning('Failed to get X mark for "{}" vs "{}"'.format(source.name, '' if target is None else target.name))
|
||||
# Silently skip this mark, otherwise other marks and legend display will fail
|
||||
|
||||
@@ -295,12 +295,16 @@ class GraphControlPanel(wx.Panel):
|
||||
|
||||
self.ySubSelection.Clear()
|
||||
for yDef in view.yDefs:
|
||||
if yDef.hidden and not self.graphFrame.includeHidden:
|
||||
continue
|
||||
self.ySubSelection.Append(self.formatLabel(yDef, selector=True), yDef)
|
||||
self.ySubSelection.Enable(len(view.yDefs) > 1)
|
||||
self.ySubSelection.SetSelection(selectedY)
|
||||
|
||||
self.xSubSelection.Clear()
|
||||
for xDef in view.xDefs:
|
||||
if xDef.hidden and not self.graphFrame.includeHidden:
|
||||
continue
|
||||
self.xSubSelection.Append(self.formatLabel(xDef, selector=True), xDef)
|
||||
self.xSubSelection.Enable(len(view.xDefs) > 1)
|
||||
self.xSubSelection.SetSelection(selectedX)
|
||||
|
||||
@@ -50,6 +50,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.includeHidden = includeHidden
|
||||
|
||||
self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui')))
|
||||
|
||||
@@ -74,7 +75,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
|
||||
# Setup - graph selector
|
||||
for view in FitGraph.views:
|
||||
if view.hidden and not includeHidden:
|
||||
if view.hidden and not self.includeHidden:
|
||||
continue
|
||||
self.graphSelection.Append(view.name, view())
|
||||
self.graphSelection.SetSelection(0)
|
||||
|
||||
@@ -316,7 +316,7 @@ class ImplantDisplay(d.Display):
|
||||
implants.append(implant)
|
||||
return implants
|
||||
|
||||
def addImplantSet(self, impSet):
|
||||
def addImplants(self, implants):
|
||||
self.mainFrame.command.Submit(cmd.GuiAddImplantSetCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
itemIDs=[i.itemID for i in impSet.implants]))
|
||||
itemIDs=[i.itemID for i in implants]))
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
import wx
|
||||
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.implantSet import ImplantSets as s_ImplantSets
|
||||
from service.market import Market
|
||||
from service.implantSet import ImplantSets as UserImplantSets
|
||||
from service.precalcImplantSet import PrecalcedImplantSets
|
||||
|
||||
|
||||
class ImplantSetApply(ContextMenuUnconditional):
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
|
||||
sIS = s_ImplantSets.getInstance()
|
||||
implantSets = sIS.getImplantSetList()
|
||||
self.userImplantSets = UserImplantSets.getInstance().getImplantSetList()
|
||||
self.structedImplantSets = PrecalcedImplantSets.getStructuredSets()
|
||||
|
||||
if len(implantSets) == 0:
|
||||
if len(self.userImplantSets) == 0 and len(self.structedImplantSets) == 0:
|
||||
return False
|
||||
|
||||
return srcContext in ("implantItemMisc", "implantEditor")
|
||||
@@ -20,34 +22,87 @@ class ImplantSetApply(ContextMenuUnconditional):
|
||||
def getText(self, callingWindow, context):
|
||||
return "Apply Implant Set"
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
bindmenu = rootMenu if "wxMSW" in wx.PlatformInfo else m
|
||||
def _addSeparator(self, m, text):
|
||||
id_ = ContextMenuUnconditional.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
sIS = s_ImplantSets.getInstance()
|
||||
implantSets = sIS.getImplantSetList()
|
||||
def _addSet(self, parentMenu, profile, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.eventSetMap[id] = profile
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _addCategory(self, parentMenu, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def _gradeSorter(self, item):
|
||||
order = ['low-grade', 'mid-grade', 'high-grade']
|
||||
try:
|
||||
pos = order.index(item.lower())
|
||||
except IndexError:
|
||||
pos = len(order)
|
||||
return pos, item
|
||||
|
||||
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
|
||||
msw = "wxMSW" in wx.PlatformInfo
|
||||
menu_lvl1 = wx.Menu()
|
||||
|
||||
self.context = context
|
||||
self.callingWindow = callingWindow
|
||||
|
||||
self.idmap = {}
|
||||
self.eventSetMap = {}
|
||||
|
||||
for set in sorted(implantSets, key=lambda i: i.name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, set.name)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||
self.idmap[id] = set
|
||||
m.Append(mitem)
|
||||
# Auto-generated sets
|
||||
for setName in sorted(self.structedImplantSets):
|
||||
setData = self.structedImplantSets[setName]
|
||||
if len(setData) == 1:
|
||||
for implantIDs in setData.values():
|
||||
menuitem_lvl1 = self._addSet(rootMenu, implantIDs, setName)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
else:
|
||||
menuitem_lvl1 = self._addCategory(rootMenu, setName)
|
||||
menu_lvl2 = wx.Menu()
|
||||
for gradeName in sorted(setData, key=self._gradeSorter):
|
||||
implantIDs = setData[gradeName]
|
||||
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantIDs, gradeName)
|
||||
menu_lvl2.Append(menuitem_lvl2)
|
||||
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
menuitem_lvl1.SetSubMenu(menu_lvl2)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
|
||||
return m
|
||||
# Separator
|
||||
if self.userImplantSets and self.structedImplantSets:
|
||||
menu_lvl1.AppendSeparator()
|
||||
|
||||
# Saved sets
|
||||
if self.userImplantSets:
|
||||
menuitem_lvl1 = self._addCategory(rootMenu, 'Saved Sets')
|
||||
menu_lvl2 = wx.Menu()
|
||||
for implantSet in sorted(self.userImplantSets, key=lambda i: i.name):
|
||||
menuitem_lvl2 = self._addSet(rootMenu if msw else menu_lvl1, implantSet, implantSet.name)
|
||||
menu_lvl2.Append(menuitem_lvl2)
|
||||
menu_lvl2.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
menuitem_lvl1.SetSubMenu(menu_lvl2)
|
||||
menu_lvl1.Append(menuitem_lvl1)
|
||||
|
||||
menu_lvl1.Bind(wx.EVT_MENU, self.handleSelection)
|
||||
return menu_lvl1
|
||||
|
||||
def handleSelection(self, event):
|
||||
impSet = self.idmap.get(event.Id, None)
|
||||
impSet = self.eventSetMap.get(event.Id, None)
|
||||
if impSet is None:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
self.callingWindow.addImplantSet(impSet)
|
||||
if isinstance(impSet, str):
|
||||
implants = PrecalcedImplantSets.stringToImplants(impSet)
|
||||
else:
|
||||
implants = impSet.implants
|
||||
self.callingWindow.addImplants(implants)
|
||||
|
||||
|
||||
ImplantSetApply.register()
|
||||
|
||||
@@ -26,7 +26,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
|
||||
return 'Add Target Profile'
|
||||
|
||||
def handleProfileAdd(self, event):
|
||||
profile = self.profileEventMap.get(event.Id, False)
|
||||
profile = self.eventProfileMap.get(event.Id, False)
|
||||
if profile is False:
|
||||
event.Skip()
|
||||
return
|
||||
@@ -34,7 +34,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
|
||||
|
||||
def _addProfile(self, parentMenu, profile, name):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.profileEventMap[id] = profile
|
||||
self.eventProfileMap[id] = profile
|
||||
menuItem = wx.MenuItem(parentMenu, id, name)
|
||||
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
|
||||
return menuItem
|
||||
@@ -51,7 +51,7 @@ class TargetProfileAdder(ContextMenuUnconditional):
|
||||
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
|
||||
profiles.sort(key=lambda p: smartSort(p.fullName))
|
||||
|
||||
self.profileEventMap = {}
|
||||
self.eventProfileMap = {}
|
||||
items = (OrderedDict(), OrderedDict())
|
||||
for profile in profiles:
|
||||
container = items
|
||||
|
||||
@@ -112,6 +112,8 @@ class ItemAffectedBy(wx.Panel):
|
||||
else:
|
||||
try:
|
||||
self.affectedBy.CollapseAll()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
|
||||
first = True
|
||||
for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||
if m.baseValue == 0:
|
||||
continue
|
||||
if not first:
|
||||
sizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||
first = False
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from eos.saveddata.module import Module
|
||||
import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
from config import slotColourMap
|
||||
from eos.saveddata.module import Module
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.display import Display
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.attribute import Attribute
|
||||
from service.fit import Fit
|
||||
from config import slotColourMap
|
||||
from service.market import Market
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -49,8 +50,6 @@ class ItemView(Display):
|
||||
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
|
||||
# Make reverse map, used by sorter
|
||||
self.metaMap = self.makeReverseMetaMap()
|
||||
self.active = []
|
||||
|
||||
def delaySearch(self, evt):
|
||||
@@ -172,15 +171,15 @@ class ItemView(Display):
|
||||
def scheduleSearch(self, event=None):
|
||||
self.searchTimer.Stop() # Cancel any pending timers
|
||||
search = self.marketBrowser.search.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Re-select market group if search query has zero length
|
||||
if len(realsearch) == 0:
|
||||
self.selectionMade('search')
|
||||
return
|
||||
|
||||
self.marketBrowser.mode = 'search'
|
||||
self.sMkt.searchItems(search, self.populateSearch)
|
||||
self.sMkt.searchItems(search, self.populateSearch, 'market')
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
# Wipe item store and update everything to accomodate with it
|
||||
@@ -195,10 +194,11 @@ class ItemView(Display):
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
# If we're no longer searching, dump the results
|
||||
if self.marketBrowser.mode != 'search':
|
||||
return
|
||||
items = Market.getItems(itemIDs)
|
||||
self.updateItemStore(items)
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
@@ -214,7 +214,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 +259,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)
|
||||
|
||||
@@ -25,7 +25,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
|
||||
helpCursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
|
||||
@@ -86,8 +86,8 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
|
||||
def OnScheduleSearch(self, event):
|
||||
search = self.BrowserSearchBox.GetValue()
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
minChars = 1 if isStringCjk(realsearch) else 3
|
||||
if len(realsearch) >= minChars:
|
||||
self.lastSearch = search
|
||||
|
||||
@@ -608,6 +608,18 @@ class Miscellanea(ViewColumn):
|
||||
shield_hp = stuff.getModifiedItemAttr("shieldBonus", 0)
|
||||
hp = max(stuff_hp, armor_hp * cycles, capacitor_hp * cycles, shield_hp * cycles, 0)
|
||||
|
||||
nonChargedMap = {
|
||||
"Ancillary Remote Armor Repairer": ("armor", "Armor repaired per second"),
|
||||
"Ancillary Remote Shield Booster": ("shield", "Shield transferred per second")}
|
||||
if not cycles and itemGroup in nonChargedMap:
|
||||
rps = stuff.getRemoteReps(ignoreState=True)
|
||||
rps = getattr(rps, nonChargedMap[itemGroup][0])
|
||||
if not rps:
|
||||
return "", None
|
||||
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
|
||||
tooltip = nonChargedMap[itemGroup][1]
|
||||
return text, tooltip
|
||||
|
||||
if not hp or not cycleTime or not cycles:
|
||||
return "", None
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -812,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)
|
||||
@@ -837,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)
|
||||
|
||||
@@ -302,21 +302,21 @@ class ItemView(d.Display):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
search = self.searchBox.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Show nothing if query is too short
|
||||
if len(realsearch) < 3:
|
||||
self.clearSearch()
|
||||
return
|
||||
|
||||
sMkt.searchItems(search, self.populateSearch, ["Implant"])
|
||||
sMkt.searchItems(search, self.populateSearch, 'implants')
|
||||
|
||||
def populateSearch(self, items):
|
||||
def populateSearch(self, itemIDs):
|
||||
if not self.IsShown():
|
||||
self.parent.availableImplantsTree.Hide()
|
||||
self.Show()
|
||||
self.parent.Layout()
|
||||
|
||||
items = Market.getItems(itemIDs)
|
||||
items = [i for i in items if i.group.name != 'Booster']
|
||||
self.items = sorted(list(items), key=lambda i: i.name)
|
||||
|
||||
|
||||
@@ -475,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:
|
||||
@@ -727,12 +729,12 @@ class ImplantEditorView(BaseImplantEditorView):
|
||||
|
||||
sChar.removeImplant(char.ID, implant)
|
||||
|
||||
def addImplantSet(self, impSet):
|
||||
def addImplants(self, implants):
|
||||
charEditor = self.Parent.Parent
|
||||
char = charEditor.entityEditor.getActiveEntity()
|
||||
|
||||
sChar = Character.getInstance()
|
||||
for implant in impSet.implants:
|
||||
for implant in implants:
|
||||
sChar.addImplant(char.ID, implant.item.ID)
|
||||
|
||||
wx.PostEvent(charEditor, GE.CharChanged())
|
||||
@@ -831,7 +833,7 @@ class APIView(wx.Panel):
|
||||
def fetchSkills(self, evt):
|
||||
sChar = Character.getInstance()
|
||||
char = self.charEditor.entityEditor.getActiveEntity()
|
||||
sChar.apiFetch(char.ID, self.__fetchCallback)
|
||||
sChar.apiFetch(char.ID, APIView.fetchCallback)
|
||||
|
||||
def addCharacter(self, event):
|
||||
sEsi = Esi.getInstance()
|
||||
@@ -839,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
|
||||
@@ -897,7 +899,8 @@ class APIView(wx.Panel):
|
||||
if event is not None:
|
||||
event.Skip()
|
||||
|
||||
def __fetchCallback(self, e=None):
|
||||
@staticmethod
|
||||
def fetchCallback(e=None):
|
||||
if e:
|
||||
pyfalog.warn("Error fetching skill information for character for __fetchCallback")
|
||||
exc_type, exc_value, exc_trace = e
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -5,11 +5,14 @@ import requests
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import config
|
||||
import gui.globalEvents as GE
|
||||
from eos.db import getItem
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.display import Display
|
||||
from gui.characterEditor import APIView
|
||||
from service.character import Character
|
||||
from service.esi import Esi
|
||||
from service.esiAccess import APIException
|
||||
from service.fit import Fit
|
||||
@@ -125,6 +128,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 +307,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)
|
||||
@@ -331,7 +338,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
self.addBtn = wx.Button(self, wx.ID_ANY, "Add Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.addBtn, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
self.deleteBtn = wx.Button(self, wx.ID_ANY, "Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.deleteBtn = wx.Button(self, wx.ID_ANY, "Remove Character", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
mainSizer.Add(btnSizer, 0, wx.EXPAND, 5)
|
||||
@@ -351,6 +358,16 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
|
||||
def ssoLogin(self, event):
|
||||
self.popCharList()
|
||||
sChar = Character.getInstance()
|
||||
# Update existing pyfa character, if it doesn't exist - create new
|
||||
char = sChar.getCharacter(event.character.characterName)
|
||||
newChar = False
|
||||
if char is None:
|
||||
char = sChar.new(event.character.characterName)
|
||||
newChar = True
|
||||
char.setSsoCharacter(event.character, config.getClientSecret())
|
||||
sChar.apiFetch(char.ID, APIView.fetchCallback)
|
||||
wx.PostEvent(self.mainFrame, GE.CharListUpdated())
|
||||
event.Skip()
|
||||
|
||||
def kbEvent(self, event):
|
||||
@@ -381,6 +398,8 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
try:
|
||||
sEsi = Esi.getInstance()
|
||||
sEsi.login()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as ex:
|
||||
ESIServerExceptionHandler(self, ex)
|
||||
|
||||
@@ -457,6 +476,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)
|
||||
|
||||
@@ -40,9 +40,6 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
position=fit.modules.index(oldMod),
|
||||
newModInfo=self.newModInfo)
|
||||
return self.subsystemCmd.Do()
|
||||
if not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
return False
|
||||
fit.modules.append(newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to append to list')
|
||||
@@ -52,6 +49,13 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
# fits() sometimes relies on recalculated on-item attributes, such as fax cap
|
||||
# booster limitation, so we have to check it after recalculating and remove the
|
||||
# module if the check has failed
|
||||
if not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
self.Undo()
|
||||
return False
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
return True
|
||||
|
||||
@@ -63,7 +67,7 @@ class CalcAddLocalModuleCommand(wx.Command):
|
||||
if self.savedPosition is None:
|
||||
return False
|
||||
from .localRemove import CalcRemoveLocalModulesCommand
|
||||
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.savedPosition], recalc=False)
|
||||
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.savedPosition], recalc=False, clearTail=True)
|
||||
if not cmd.Do():
|
||||
return False
|
||||
restoreCheckedStates(Fit.getInstance().getFit(self.fitID), self.savedStateCheckChanges)
|
||||
|
||||
@@ -3,7 +3,7 @@ from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
from eos.const import FittingSlot
|
||||
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates
|
||||
from gui.fitCommands.helpers import ModuleInfo, restoreCheckedStates, restoreRemovedDummies
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
|
||||
def __init__(self, fitID, positions, recalc=True):
|
||||
def __init__(self, fitID, positions, recalc=True, clearTail=False):
|
||||
wx.Command.__init__(self, True, 'Remove Module')
|
||||
self.fitID = fitID
|
||||
self.positions = positions
|
||||
self.recalc = recalc
|
||||
self.clearTail = clearTail
|
||||
self.savedSubInfos = None
|
||||
self.savedModInfos = None
|
||||
self.savedStateCheckChanges = None
|
||||
self.savedTail = None
|
||||
|
||||
def Do(self):
|
||||
pyfalog.debug('Doing removal of local modules from positions {} on fit {}'.format(self.positions, self.fitID))
|
||||
@@ -40,6 +42,9 @@ class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
if len(self.savedSubInfos) == 0 and len(self.savedModInfos) == 0:
|
||||
return False
|
||||
|
||||
if self.clearTail:
|
||||
self.savedTail = fit.clearTail()
|
||||
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
@@ -76,6 +81,7 @@ class CalcRemoveLocalModulesCommand(wx.Command):
|
||||
if not any(results):
|
||||
return False
|
||||
restoreCheckedStates(fit, self.savedStateCheckChanges)
|
||||
restoreRemovedDummies(fit, self.savedTail)
|
||||
return True
|
||||
|
||||
@property
|
||||
|
||||
@@ -39,7 +39,17 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
||||
if newMod.slot != oldMod.slot:
|
||||
return False
|
||||
# Dummy it out in case the next bit fails
|
||||
fit.modules.free(self.position)
|
||||
fit.modules.replace(self.position, newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to replace in list')
|
||||
self.Undo()
|
||||
return False
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
if not self.ignoreRestrictions and not newMod.fits(fit):
|
||||
pyfalog.warning('Module does not fit')
|
||||
self.Undo()
|
||||
@@ -52,17 +62,6 @@ class CalcReplaceLocalModuleCommand(wx.Command):
|
||||
pyfalog.warning('Invalid charge')
|
||||
self.Undo()
|
||||
return False
|
||||
fit.modules.replace(self.position, newMod)
|
||||
if newMod not in fit.modules:
|
||||
pyfalog.warning('Failed to replace in list')
|
||||
self.Undo()
|
||||
return False
|
||||
if self.recalc:
|
||||
# Need to flush because checkStates sometimes relies on module->fit
|
||||
# relationship via .owner attribute, which is handled by SQLAlchemy
|
||||
eos.db.flush()
|
||||
sFit.recalc(fit)
|
||||
self.savedStateCheckChanges = sFit.checkStates(fit, newMod)
|
||||
return True
|
||||
|
||||
def Undo(self):
|
||||
|
||||
@@ -353,6 +353,8 @@ def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
|
||||
|
||||
|
||||
def restoreRemovedDummies(fit, dummyInfo):
|
||||
if dummyInfo is None:
|
||||
return
|
||||
# Need this to properly undo the case when removal of subsystems removes dummy slots
|
||||
for position in sorted(dummyInfo):
|
||||
slot = dummyInfo[position]
|
||||
|
||||
@@ -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
|
||||
@@ -251,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
|
||||
@@ -669,8 +678,8 @@ class MainFrame(wx.Frame):
|
||||
|
||||
def toggleOverrides(self, event):
|
||||
ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled
|
||||
|
||||
wx.PostEvent(self, GE.FitChanged(fitIDs=(self.getActiveFit(),)))
|
||||
changedFitIDs = Fit.getInstance().processOverrideToggle()
|
||||
wx.PostEvent(self, GE.FitChanged(fitIDs=changedFitIDs))
|
||||
menu = self.GetMenuBar()
|
||||
menu.SetLabel(menu.toggleOverridesId,
|
||||
"&Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "&Turn Overrides On")
|
||||
@@ -747,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]])):
|
||||
@@ -771,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:
|
||||
|
||||
@@ -80,8 +80,8 @@ class MainMenuBar(wx.MenuBar):
|
||||
fitMenu = wx.Menu()
|
||||
self.Append(fitMenu, "Fi&t")
|
||||
|
||||
fitMenu.Append(wx.ID_UNDO)
|
||||
fitMenu.Append(wx.ID_REDO)
|
||||
fitMenu.Append(wx.ID_UNDO, "&Undo\tCTRL+Z", "Undo the most recent action")
|
||||
fitMenu.Append(wx.ID_REDO, "&Redo\tCTRL+Y", "Redo the most recent undone action")
|
||||
|
||||
fitMenu.AppendSeparator()
|
||||
fitMenu.Append(wx.ID_COPY, "&To Clipboard\tCTRL+C", "Export a fit to the clipboard")
|
||||
|
||||
@@ -67,7 +67,8 @@ class DmgPatternEntityEditor(EntityEditor):
|
||||
def getEntitiesFromContext(self):
|
||||
sDP = DamagePattern.getInstance()
|
||||
choices = sorted(sDP.getUserDamagePatternList(), key=lambda p: p.rawName)
|
||||
return [c for c in choices if c.rawName != "Selected Ammo"]
|
||||
choices = [c for c in choices if c.rawName != "Selected Ammo"]
|
||||
return choices
|
||||
|
||||
def DoNew(self, name):
|
||||
sDP = DamagePattern.getInstance()
|
||||
@@ -183,6 +184,10 @@ class DmgPatternEditor(AuxiliaryFrame):
|
||||
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
|
||||
btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower())))
|
||||
|
||||
if not self.entityEditor.checkEntitiesExist():
|
||||
self.Close()
|
||||
return
|
||||
|
||||
self.Layout()
|
||||
bsize = self.GetBestSize()
|
||||
self.SetSize((-1, bsize.height))
|
||||
@@ -232,6 +237,11 @@ class DmgPatternEditor(AuxiliaryFrame):
|
||||
self.entityEditor.btnDelete.Enable()
|
||||
|
||||
def patternChanged(self, event=None):
|
||||
|
||||
if not self.entityEditor.checkEntitiesExist():
|
||||
self.Close()
|
||||
return
|
||||
|
||||
p = self.entityEditor.getActiveEntity()
|
||||
|
||||
if p is None:
|
||||
@@ -265,6 +275,8 @@ class DmgPatternEditor(AuxiliaryFrame):
|
||||
except ImportError as e:
|
||||
pyfalog.error(e)
|
||||
self.stNotice.SetLabel(str(e))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
msg = "Could not import from clipboard: unknown errors"
|
||||
pyfalog.warning(msg)
|
||||
|
||||
@@ -13,6 +13,7 @@ from eos.db.gamedata.queries import getAttributeInfo, getItem
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.marketBrowser import SearchBox
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
@@ -168,50 +169,80 @@ class ItemView(d.Display):
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent)
|
||||
sMkt = Market.getInstance()
|
||||
self.activeItems = []
|
||||
|
||||
self.things = sMkt.getItemsWithOverrides()
|
||||
self.items = self.things
|
||||
self.searchTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.scheduleSearch, self.searchTimer)
|
||||
|
||||
self.searchBox = parent.Parent.Parent.searchBox
|
||||
# Bind search actions
|
||||
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.delaySearch)
|
||||
|
||||
self.update(self.items)
|
||||
self.update(Market.getInstance().getItemsWithOverrides())
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
if event:
|
||||
self.searchBox.Clear()
|
||||
self.items = self.things
|
||||
self.update(self.items)
|
||||
self.update(Market.getInstance().getItemsWithOverrides())
|
||||
|
||||
def updateItems(self, updateDisplay=False):
|
||||
sMkt = Market.getInstance()
|
||||
self.things = sMkt.getItemsWithOverrides()
|
||||
self.items = self.things
|
||||
if updateDisplay:
|
||||
self.update(self.things)
|
||||
self.update(Market.getInstance().getItemsWithOverrides())
|
||||
|
||||
def delaySearch(self, evt):
|
||||
sFit = Fit.getInstance()
|
||||
self.searchTimer.Stop()
|
||||
self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True)
|
||||
|
||||
def scheduleSearch(self, event=None):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
search = self.searchBox.GetLineText(0)
|
||||
# Make sure we do not count wildcard as search symbol
|
||||
realsearch = search.replace("*", "")
|
||||
# Make sure we do not count wildcards as search symbol
|
||||
realsearch = search.replace('*', '').replace('?', '')
|
||||
# Show nothing if query is too short
|
||||
if len(realsearch) < 3:
|
||||
self.clearSearch()
|
||||
return
|
||||
|
||||
sMkt.searchItems(search, self.populateSearch, False)
|
||||
sMkt.searchItems(search, self.populateSearch, 'everything')
|
||||
|
||||
def populateSearch(self, items):
|
||||
self.items = list(items)
|
||||
def itemSort(self, item):
|
||||
sMkt = Market.getInstance()
|
||||
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return not isFittable, catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
def populateSearch(self, itemIDs):
|
||||
items = Market.getItems(itemIDs)
|
||||
self.update(items)
|
||||
|
||||
def populate(self, items):
|
||||
if len(items) > 0:
|
||||
self.unselectAll()
|
||||
items.sort(key=self.itemSort)
|
||||
self.activeItems = items
|
||||
d.Display.populate(self, items)
|
||||
|
||||
def refresh(self, items):
|
||||
if len(items) > 1:
|
||||
items.sort(key=self.itemSort)
|
||||
d.Display.refresh(self, items)
|
||||
|
||||
|
||||
class AttributeGrid(wxpg.PropertyGrid):
|
||||
def __init__(self, parent):
|
||||
@@ -236,7 +267,7 @@ class AttributeGrid(wxpg.PropertyGrid):
|
||||
self.Clear()
|
||||
self.btn.Enable(True)
|
||||
sel = event.EventObject.GetFirstSelected()
|
||||
self.item = item = self.itemView.items[sel]
|
||||
self.item = item = self.itemView.activeItems[sel]
|
||||
|
||||
for key in sorted(item.attributes.keys()):
|
||||
override = item.overrides.get(key, None)
|
||||
|
||||
@@ -211,6 +211,8 @@ class ImplantSetEditor(AuxiliaryFrame):
|
||||
except ImportError as e:
|
||||
pyfalog.error(e)
|
||||
self.stNotice.SetLabel(str(e))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
pyfalog.error(e)
|
||||
self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
|
||||
|
||||
@@ -25,12 +25,12 @@ from collections import OrderedDict
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
|
||||
from gui.utils.clipboard import toClipboard, fromClipboard
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
from gui.utils.inputs import FloatBox, InputValidator, strToFloat
|
||||
from service.fit import Fit
|
||||
from service.targetProfile import TargetProfile
|
||||
@@ -352,6 +352,8 @@ class TargetProfileEditor(AuxiliaryFrame):
|
||||
except ImportError as e:
|
||||
pyfalog.error(e)
|
||||
self.stNotice.SetLabel(str(e))
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as e:
|
||||
msg = "Could not import from clipboard:"
|
||||
pyfalog.warning(msg)
|
||||
|
||||
@@ -70,6 +70,8 @@ class exportHtmlThread(threading.Thread):
|
||||
except IOError as ex:
|
||||
pyfalog.warning("Failed to write to " + settings.getPath())
|
||||
pass
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
@@ -226,6 +228,8 @@ class exportHtmlThread(threading.Thread):
|
||||
|
||||
HTMLfit += ' </ul>\n </li>\n'
|
||||
HTMLship += HTMLfit
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pyfalog.warning("Failed to export line")
|
||||
continue
|
||||
@@ -281,6 +285,8 @@ class exportHtmlThread(threading.Thread):
|
||||
HTML += '<a class="outOfGameBrowserLink" target="_blank" href="' + dnaUrl + dnaFit + '">' \
|
||||
+ ship.name + ': ' + \
|
||||
fit[1] + '</a><br> \n'
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
pyfalog.error("Failed to export line")
|
||||
continue
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import math
|
||||
|
||||
from eos.utils.round import roundToPrec, roundDec
|
||||
|
||||
|
||||
def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=False, unitName=None):
|
||||
"""
|
||||
@@ -97,29 +99,3 @@ def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=Fal
|
||||
else:
|
||||
result = "{}{} {}{}".format(sign, mantissa, suffix, unitName)
|
||||
return result
|
||||
|
||||
|
||||
def roundToPrec(val, prec, nsValue=None):
|
||||
"""
|
||||
nsValue: custom value which should be used to determine normalization shift
|
||||
"""
|
||||
# We're not rounding integers anyway
|
||||
# Also make sure that we do not ask to calculate logarithm of zero
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1)
|
||||
# But we don't want to round integers
|
||||
if roundFactor < 0:
|
||||
roundFactor = 0
|
||||
# Do actual rounding
|
||||
val = round(val, roundFactor)
|
||||
# Make sure numbers with .0 part designating float don't get through
|
||||
if int(val) == val:
|
||||
val = int(val)
|
||||
return val
|
||||
|
||||
|
||||
def roundDec(val, prec):
|
||||
if int(val) == val:
|
||||
return int(val)
|
||||
return round(val, prec)
|
||||
|
||||
|
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 590 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 779 B |
|
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 778 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 732 B |
|
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 438 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 897 B After Width: | Height: | Size: 693 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 137 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 142 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 142 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 142 B |
|
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 577 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 609 B After Width: | Height: | Size: 305 B |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 153 B |
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 113 B |
|
Before Width: | Height: | Size: 253 B After Width: | Height: | Size: 72 B |
|
Before Width: | Height: | Size: 316 B After Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.3 KiB |