Merge remote-tracking branch 'origin/master' into attrGroup
# Conflicts: # eve.db
This commit is contained in:
145
.appveyor.yml
145
.appveyor.yml
@@ -1,5 +1,4 @@
|
|||||||
environment:
|
environment:
|
||||||
|
|
||||||
global:
|
global:
|
||||||
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
|
# 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
|
# /E:ON and /V:ON options are not enabled in the batch script intepreter
|
||||||
@@ -8,76 +7,11 @@ environment:
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
|
||||||
# Python 2.7.10 is the latest version and is not pre-installed.
|
- PYTHON: "C:\\Python36"
|
||||||
|
PYTHON_VERSION: "3.6.x"
|
||||||
- PYTHON: "C:\\Python27.10"
|
|
||||||
PYTHON_VERSION: "2.7.10"
|
|
||||||
PYTHON_ARCH: "32"
|
PYTHON_ARCH: "32"
|
||||||
|
init:
|
||||||
#- PYTHON: "C:\\Python27.10-x64"
|
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||||
# PYTHON_VERSION: "2.7.10"
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
# Pre-installed Python versions, which Appveyor may upgrade to
|
|
||||||
# a later point release.
|
|
||||||
# See: http://www.appveyor.com/docs/installed-software#python
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python27"
|
|
||||||
# PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python27-x64"
|
|
||||||
# PYTHON_VERSION: "2.7.x" # currently 2.7.9
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python33"
|
|
||||||
# PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python33-x64"
|
|
||||||
# PYTHON_VERSION: "3.3.x" # currently 3.3.5
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python34"
|
|
||||||
# PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python34-x64"
|
|
||||||
# PYTHON_VERSION: "3.4.x" # currently 3.4.3
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
# Python versions not pre-installed
|
|
||||||
|
|
||||||
# Python 2.6.6 is the latest Python 2.6 with a Windows installer
|
|
||||||
# See: https://github.com/ogrisel/python-appveyor-demo/issues/10
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python266"
|
|
||||||
# PYTHON_VERSION: "2.6.6"
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python266-x64"
|
|
||||||
# PYTHON_VERSION: "2.6.6"
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python35"
|
|
||||||
# PYTHON_VERSION: "3.5.0"
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python35-x64"
|
|
||||||
# PYTHON_VERSION: "3.5.0"
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
# Major and minor releases (i.e x.0.0 and x.y.0) prior to 3.3.0 use
|
|
||||||
# a different naming scheme.
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python270"
|
|
||||||
# PYTHON_VERSION: "2.7.0"
|
|
||||||
# PYTHON_ARCH: "32"
|
|
||||||
|
|
||||||
#- PYTHON: "C:\\Python270-x64"
|
|
||||||
# PYTHON_VERSION: "2.7.0"
|
|
||||||
# PYTHON_ARCH: "64"
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# If there is a newer build queued for the same PR, cancel this one.
|
# 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
|
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
||||||
@@ -89,34 +23,23 @@ install:
|
|||||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||||
throw "There are newer queued builds for this pull request, failing early." }
|
throw "There are newer queued builds for this pull request, failing early." }
|
||||||
|
|
||||||
# Install wxPython
|
|
||||||
- 'ECHO Downloading wxPython.'
|
|
||||||
- "appveyor DownloadFile https://goo.gl/yvO8PB -FileName C:\\wxpython.exe"
|
|
||||||
#- "appveyor DownloadFile https://goo.gl/Uj0jV3 -FileName C:\\wxpython64.exe"
|
|
||||||
|
|
||||||
- 'ECHO Install wxPython'
|
|
||||||
- "C:\\wxpython.exe /SP- /VERYSILENT /NORESTART"
|
|
||||||
#- "C:\\wxpython64.exe /SP- /VERYSILENT /NORESTART"
|
|
||||||
|
|
||||||
- ECHO "Filesystem root:"
|
- ECHO "Filesystem root:"
|
||||||
- ps: "ls \"C:/\""
|
- ps: "ls \"C:/\""
|
||||||
|
|
||||||
|
- ECHO "Filesystem projects root:"
|
||||||
|
- ps: "ls \"C:\\projects\\\""
|
||||||
|
|
||||||
- ECHO "Filesystem pyfa root:"
|
- ECHO "Filesystem pyfa root:"
|
||||||
- ps: "ls \"C:\\projects\\pyfa\\\""
|
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\""
|
||||||
|
|
||||||
- ECHO "Installed SDKs:"
|
- ECHO "Installed SDKs:"
|
||||||
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
- ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
|
||||||
|
|
||||||
# Install Python (from the official .msi of http://python.org) and pip when
|
|
||||||
# not already installed.
|
|
||||||
# - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 }
|
|
||||||
|
|
||||||
# Prepend newly installed Python to the PATH of this build (this cannot be
|
# Prepend newly installed Python to the PATH of this build (this cannot be
|
||||||
# done from inside the powershell script as it would require to restart
|
# done from inside the powershell script as it would require to restart
|
||||||
# the parent CMD process).
|
# the parent CMD process).
|
||||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||||
|
|
||||||
# Check that we have the expected version and architecture for Python
|
|
||||||
- "python --version"
|
- "python --version"
|
||||||
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
|
||||||
|
|
||||||
@@ -128,19 +51,36 @@ install:
|
|||||||
# compiled extensions and are not provided as pre-built wheel packages,
|
# compiled extensions and are not provided as pre-built wheel packages,
|
||||||
# pip will build them from source using the MSVC compiler matching the
|
# pip will build them from source using the MSVC compiler matching the
|
||||||
# target Python version and architecture
|
# target Python version and architecture
|
||||||
# C:\\projects\\eve-gnosis\\
|
|
||||||
- ECHO "Install pip requirements:"
|
- ECHO "Install pip requirements:"
|
||||||
- "pip install -r requirements.txt"
|
- "pip install -r requirements.txt"
|
||||||
- "pip install -r requirements_test.txt"
|
- "pip install PyInstaller"
|
||||||
- "pip install -r requirements_build_windows.txt"
|
|
||||||
|
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)
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
# Build the compiled extension
|
|
||||||
# - "python setup.py build"
|
|
||||||
- ECHO "Build pyfa:"
|
- ECHO "Build pyfa:"
|
||||||
#- copy C:\projects\pyfa\dist_assets\win\pyfa.spec C:\projects\pyfa\pyfa.spec
|
|
||||||
- "python C:\\projects\\pyfa\\setup.py build"
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# PyInstaller - create binaries for pyfa
|
||||||
|
##########
|
||||||
|
# Build command for PyInstaller
|
||||||
|
- "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"
|
||||||
|
- ps: dir $env:PYFA_DIST_DIR/
|
||||||
#- ECHO "Build pyfa (Debug):"
|
#- ECHO "Build pyfa (Debug):"
|
||||||
#- copy C:\projects\pyfa\dist_assets\win\pyfa_debug.spec C:\projects\pyfa\pyfa_debug.spec
|
#- 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"
|
#- "pyinstaller.exe --clean --noconfirm --windowed --upx-dir=C:\\projects\\pyfa\\scripts\\upx.exe C:\\projects\\pyfa\\pyfa_debug.spec"
|
||||||
@@ -150,12 +90,11 @@ build: on
|
|||||||
after_build:
|
after_build:
|
||||||
- ps: "ls \"./\""
|
- ps: "ls \"./\""
|
||||||
#- ps: "ls \"C:\\projects\\pyfa\\build\\pyfa\\\""
|
#- ps: "ls \"C:\\projects\\pyfa\\build\\pyfa\\\""
|
||||||
- ps: "ls \"C:\\projects\\pyfa\\build\\\""
|
# - ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\build\\exe.win32-2.7\\\""
|
||||||
- ps: "ls \"C:\\projects\\pyfa\\build\\exe.win32-2.7\\\""
|
|
||||||
# Zip
|
# Zip
|
||||||
# APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER
|
# APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER
|
||||||
#- 7z a build.zip -r C:\projects\pyfa\build\pyfa\*.*
|
#- 7z a build.zip -r C:\projects\pyfa\build\pyfa\*.*
|
||||||
- 7z a pyfa.zip -r C:\projects\pyfa\build\exe.win32-2.7\*.*
|
- 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\*.*
|
#- 7z a pyfa_debug.zip -r C:\projects\pyfa\dist\pyfa_debug\*.*
|
||||||
|
|
||||||
on_success:
|
on_success:
|
||||||
@@ -176,11 +115,21 @@ after_test:
|
|||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
# Archive the generated packages in the ci.appveyor.com build report.
|
# Archive the generated packages in the ci.appveyor.com build report.
|
||||||
- path: pyfa.zip
|
- path: pyfa*-win.zip
|
||||||
name: 'pyfa.zip'
|
- path: pyfa*-win.exe
|
||||||
#- path: pyfa_debug.zip
|
#- path: pyfa_debug.zip
|
||||||
# name: Pyfa_debug
|
# name: Pyfa_debug
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
tag: $(pyfa_version)
|
||||||
|
release: pyfa $(pyfa_version)
|
||||||
|
description: 'Release description'
|
||||||
|
provider: GitHub
|
||||||
|
auth_token:
|
||||||
|
secure: BfNHO66ff5hVx2O2ORbl49X0U/5h2V2T0IuRZDwm7fd1HvsVluF0wRCbl29oRp1M
|
||||||
|
draft: true
|
||||||
|
on:
|
||||||
|
APPVEYOR_REPO_TAG: true # deploy on tag push only
|
||||||
#on_success:
|
#on_success:
|
||||||
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
||||||
#
|
#
|
||||||
59
.travis.yml
59
.travis.yml
@@ -1,36 +1,29 @@
|
|||||||
dist: trusty
|
os: linux
|
||||||
sudo: required
|
|
||||||
language: python
|
language: python
|
||||||
cache: pip
|
|
||||||
python:
|
python:
|
||||||
- '3.6'
|
- 3.6
|
||||||
env:
|
matrix:
|
||||||
- TOXENV=pep8
|
include:
|
||||||
addons:
|
- os: osx
|
||||||
apt:
|
osx_image: xcode7.3
|
||||||
packages:
|
language: generic
|
||||||
|
env: PYTHON=3.6.1
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update && sudo apt-get --reinstall install -qq language-pack-en language-pack-ru language-pack-he language-pack-zh-hans
|
- bash scripts/setup-osx.sh
|
||||||
- pip install tox
|
install:
|
||||||
# We're not actually installing Tox, but have to run it before we install wxPython via Conda. This is fugly but vOv
|
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
|
||||||
- tox
|
- bash scripts/package-osx.sh
|
||||||
- pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk2/ubuntu-14.04 wxPython==4.0.0b2
|
before_deploy:
|
||||||
# # get Conda
|
- export RELEASE_PKG_FILE=$(ls *.deb)
|
||||||
# - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
|
- echo "deploying $RELEASE_PKG_FILE to GitHub releases"
|
||||||
# wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
|
deploy:
|
||||||
# else
|
provider: releases
|
||||||
# wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
|
api_key:
|
||||||
# fi
|
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=
|
||||||
# - bash miniconda.sh -b -p $HOME/miniconda
|
file_glob: true
|
||||||
# - export PATH="$HOME/miniconda/bin:$PATH"
|
file: "dist/pyfa-*.zip"
|
||||||
# - hash -r
|
skip_cleanup: true
|
||||||
# - conda config --set always_yes yes --set changeps1 no
|
draft: true
|
||||||
# - conda update -q conda
|
on:
|
||||||
# # Useful for debugging any issues with conda
|
tags: true
|
||||||
# - conda info -a
|
repo: pyfa-org/Pyfa
|
||||||
#install:
|
|
||||||
# install wxPython 3.0.0.0
|
|
||||||
# - conda install -c https://conda.anaconda.org/travis wxpython=4.0.0b2
|
|
||||||
script:
|
|
||||||
- tox
|
|
||||||
|
|
||||||
|
|||||||
23
config.py
23
config.py
@@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import yaml
|
||||||
|
|
||||||
from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \
|
from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \
|
||||||
StreamHandler, TimedRotatingFileHandler, WARNING
|
StreamHandler, TimedRotatingFileHandler, WARNING
|
||||||
@@ -22,12 +23,6 @@ debug = False
|
|||||||
# Defines if our saveddata will be in pyfa root or not
|
# Defines if our saveddata will be in pyfa root or not
|
||||||
saveInRoot = False
|
saveInRoot = False
|
||||||
|
|
||||||
# Version data
|
|
||||||
|
|
||||||
version = "2.6.1"
|
|
||||||
tag = "Stable"
|
|
||||||
expansionName = "Onslaught"
|
|
||||||
expansionVersion = "1.5"
|
|
||||||
evemonMinVersion = "4081"
|
evemonMinVersion = "4081"
|
||||||
|
|
||||||
minItemSearchLength = 3
|
minItemSearchLength = 3
|
||||||
@@ -79,12 +74,7 @@ def getPyfaRoot():
|
|||||||
|
|
||||||
|
|
||||||
def getVersion():
|
def getVersion():
|
||||||
if os.path.isfile(os.path.join(pyfaPath, '.version')):
|
return version
|
||||||
with open(os.path.join(pyfaPath, '.version')) as f:
|
|
||||||
gitVersion = f.readline()
|
|
||||||
return gitVersion
|
|
||||||
# if no version file exists, then user is running from source or not an official build
|
|
||||||
return version + " (git)"
|
|
||||||
|
|
||||||
|
|
||||||
def getDefaultSave():
|
def getDefaultSave():
|
||||||
@@ -96,11 +86,12 @@ def defPaths(customSavePath=None):
|
|||||||
global pyfaPath
|
global pyfaPath
|
||||||
global savePath
|
global savePath
|
||||||
global saveDB
|
global saveDB
|
||||||
global gameDB
|
global gameDB
|
||||||
global saveInRoot
|
global saveInRoot
|
||||||
global logPath
|
global logPath
|
||||||
global cipher
|
global cipher
|
||||||
global clientHash
|
global clientHash
|
||||||
|
global version
|
||||||
|
|
||||||
pyfalog.debug("Configuring Pyfa")
|
pyfalog.debug("Configuring Pyfa")
|
||||||
|
|
||||||
@@ -110,6 +101,12 @@ def defPaths(customSavePath=None):
|
|||||||
if pyfaPath is None:
|
if pyfaPath is None:
|
||||||
pyfaPath = getPyfaRoot()
|
pyfaPath = getPyfaRoot()
|
||||||
|
|
||||||
|
# Version data
|
||||||
|
|
||||||
|
with open(os.path.join(pyfaPath, "version.yml"), 'r') as file:
|
||||||
|
data = yaml.load(file)
|
||||||
|
version = data['version']
|
||||||
|
|
||||||
# Where we store the saved fits etc, default is the current users home directory
|
# Where we store the saved fits etc, default is the current users home directory
|
||||||
if saveInRoot is True:
|
if saveInRoot is True:
|
||||||
savePath = getattr(configforced, "savePath", None)
|
savePath = getattr(configforced, "savePath", None)
|
||||||
|
|||||||
@@ -24,11 +24,13 @@ added_files = [
|
|||||||
('../../eve.db', '.'),
|
('../../eve.db', '.'),
|
||||||
('../../README.md', '.'),
|
('../../README.md', '.'),
|
||||||
('../../LICENSE', '.'),
|
('../../LICENSE', '.'),
|
||||||
('../../.version', '.'),
|
('../../version.yml', '.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
import_these = []
|
import_these = [
|
||||||
|
'numpy.core._dtype_ctypes' # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||||
|
]
|
||||||
|
|
||||||
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||||
|
|
||||||
@@ -54,8 +56,10 @@ a = Analysis([r'../../pyfa.py'],
|
|||||||
win_no_prefer_redirects=False,
|
win_no_prefer_redirects=False,
|
||||||
win_private_assemblies=False,
|
win_private_assemblies=False,
|
||||||
cipher=block_cipher)
|
cipher=block_cipher)
|
||||||
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data,
|
pyz = PYZ(a.pure, a.zipped_data,
|
||||||
cipher=block_cipher)
|
cipher=block_cipher)
|
||||||
|
|
||||||
exe = EXE(pyz,
|
exe = EXE(pyz,
|
||||||
a.scripts,
|
a.scripts,
|
||||||
a.binaries,
|
a.binaries,
|
||||||
@@ -70,10 +74,16 @@ exe = EXE(pyz,
|
|||||||
icon=icon,
|
icon=icon,
|
||||||
)
|
)
|
||||||
|
|
||||||
app = BUNDLE(exe,
|
app = BUNDLE(
|
||||||
name='pyfa.app',
|
exe,
|
||||||
icon=icon,
|
name='pyfa.app',
|
||||||
bundle_identifier=None,
|
icon=icon,
|
||||||
info_plist={
|
bundle_identifier=None,
|
||||||
'NSHighResolutionCapable': 'True'
|
info_plist={
|
||||||
})
|
'NSHighResolutionCapable': 'True',
|
||||||
|
'NSPrincipalClass': 'NSApplication',
|
||||||
|
'CFBundleName': 'pyfa',
|
||||||
|
'CFBundleDisplayName': 'pyfa',
|
||||||
|
'CFBundleIdentifier': 'org.pyfaorg.pyfa',
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -3,44 +3,35 @@
|
|||||||
import os.path
|
import os.path
|
||||||
from subprocess import call
|
from subprocess import call
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from packaging.version import Version
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
def zipdir(path, zip):
|
with open("version.yml", 'r') as file:
|
||||||
for root, dirs, files in os.walk(path):
|
data = yaml.load(file)
|
||||||
for file in files:
|
version = data['version']
|
||||||
zip.write(os.path.join(root, file))
|
|
||||||
|
|
||||||
config = {}
|
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
||||||
|
|
||||||
exec(compile(open("config.py").read(), "config.py", 'exec'), config)
|
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 5\ISCC.exe" # inno script location via wine
|
source = os.path.join(os.environ["PYFA_DIST_DIR"], "pyfa")
|
||||||
|
|
||||||
print("Creating archive")
|
fileName = "pyfa-{}-win".format(os.environ["PYFA_VERSION"])
|
||||||
|
|
||||||
source = os.path.join(os.getcwd(), "dist", "pyfa")
|
|
||||||
|
|
||||||
fileName = "pyfa-{}-{}-{}-win".format(
|
|
||||||
config['version'],
|
|
||||||
config['expansionName'].lower(),
|
|
||||||
config['expansionVersion']
|
|
||||||
)
|
|
||||||
|
|
||||||
archive = zipfile.ZipFile(os.path.join(os.getcwd(), "dist", fileName + ".zip"), 'w', compression=zipfile.ZIP_DEFLATED)
|
|
||||||
zipdir(source, archive)
|
|
||||||
archive.close()
|
|
||||||
|
|
||||||
print("Compiling EXE")
|
print("Compiling EXE")
|
||||||
|
|
||||||
expansion = "%s %s" % (config['expansionName'], config['expansionVersion']),
|
v = Version(version)
|
||||||
|
|
||||||
|
print(v)
|
||||||
|
|
||||||
call([
|
call([
|
||||||
iscc,
|
iscc,
|
||||||
os.path.join(os.getcwd(), "dist_assets", "win", "pyfa-setup.iss"),
|
os.path.join(os.getcwd(), "dist_assets", "win", "pyfa-setup.iss"),
|
||||||
"/dMyAppVersion=%s" % (config['version']),
|
"/dMyAppVersion=%s" % v,
|
||||||
"/dMyAppExpansion=%s" % expansion,
|
|
||||||
"/dMyAppDir=%s" % source,
|
"/dMyAppDir=%s" % source,
|
||||||
"/dMyOutputDir=%s" % os.path.join(os.getcwd(), "dist"),
|
"/dMyOutputDir=%s" % os.path.join(os.getcwd()),
|
||||||
"/dMyOutputFile=%s" % fileName]) # stdout=devnull, stderr=devnull
|
"/dMyOutputFile=%s" % fileName]) # stdout=devnull, stderr=devnull
|
||||||
|
|
||||||
print("Done")
|
print("Done")
|
||||||
|
|||||||
@@ -7,15 +7,12 @@
|
|||||||
#ifndef MyAppVersion
|
#ifndef MyAppVersion
|
||||||
#define MyAppVersion "2.1.0"
|
#define MyAppVersion "2.1.0"
|
||||||
#endif
|
#endif
|
||||||
#ifndef MyAppExpansion
|
|
||||||
#define MyAppExpansion "Vanguard 1.0"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
; Other config
|
; Other config
|
||||||
|
|
||||||
#define MyAppName "pyfa"
|
#define MyAppName "pyfa"
|
||||||
#define MyAppPublisher "pyfa"
|
#define MyAppPublisher "pyfa"
|
||||||
#define MyAppURL "https://forums.eveonline.com/t/27156"
|
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
||||||
#define MyAppExeName "pyfa.exe"
|
#define MyAppExeName "pyfa.exe"
|
||||||
|
|
||||||
; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup
|
; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup
|
||||||
@@ -23,7 +20,7 @@
|
|||||||
#define MinorVersionFlag 0
|
#define MinorVersionFlag 0
|
||||||
|
|
||||||
#ifndef MyOutputFile
|
#ifndef MyOutputFile
|
||||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-'+MyAppExpansion+'-win-wx3', " ", "-"))
|
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
||||||
#endif
|
#endif
|
||||||
#ifndef MyAppDir
|
#ifndef MyAppDir
|
||||||
#define MyAppDir "pyfa"
|
#define MyAppDir "pyfa"
|
||||||
@@ -39,7 +36,7 @@
|
|||||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
AppId={{3DA39096-C08D-49CD-90E0-1D177F32C8AA}
|
AppId={{3DA39096-C08D-49CD-90E0-1D177F32C8AA}
|
||||||
AppName={#MyAppName}
|
AppName={#MyAppName}
|
||||||
AppVersion={#MyAppVersion} ({#MyAppExpansion})
|
AppVersion={#MyAppVersion}
|
||||||
AppPublisher={#MyAppPublisher}
|
AppPublisher={#MyAppPublisher}
|
||||||
AppPublisherURL={#MyAppURL}
|
AppPublisherURL={#MyAppURL}
|
||||||
AppSupportURL={#MyAppURL}
|
AppSupportURL={#MyAppURL}
|
||||||
@@ -51,10 +48,8 @@ LicenseFile={#MyAppDir}\LICENSE
|
|||||||
OutputDir={#MyOutputDir}
|
OutputDir={#MyOutputDir}
|
||||||
OutputBaseFilename={#MyOutputFile}
|
OutputBaseFilename={#MyOutputFile}
|
||||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||||
Compression=lzma
|
|
||||||
SolidCompression=yes
|
SolidCompression=yes
|
||||||
CloseApplications=yes
|
CloseApplications=yes
|
||||||
AppReadmeFile=https://github.com/pyfa-org/Pyfa/blob/v{#MyAppVersion}/readme.txt
|
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ from itertools import chain
|
|||||||
import subprocess
|
import subprocess
|
||||||
import requests.certs
|
import requests.certs
|
||||||
|
|
||||||
label = subprocess.check_output([
|
label = subprocess.check_output(["git", "describe", "--tags"]).strip()
|
||||||
"git", "describe", "--tags"]).strip()
|
|
||||||
|
|
||||||
with open('.version', 'w+') as f:
|
with open('.version', 'w+') as f:
|
||||||
f.write(label.decode())
|
f.write(label.decode())
|
||||||
@@ -18,7 +17,7 @@ added_files = [
|
|||||||
('../../imgs/gui/*.gif', 'imgs/gui'),
|
('../../imgs/gui/*.gif', 'imgs/gui'),
|
||||||
('../../imgs/icons/*.png', 'imgs/icons'),
|
('../../imgs/icons/*.png', 'imgs/icons'),
|
||||||
('../../imgs/renders/*.png', 'imgs/renders'),
|
('../../imgs/renders/*.png', 'imgs/renders'),
|
||||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||||
('../../dist_assets/win/pyfa.ico', '.'),
|
('../../dist_assets/win/pyfa.ico', '.'),
|
||||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||||
@@ -26,10 +25,12 @@ added_files = [
|
|||||||
('../../eve.db', '.'),
|
('../../eve.db', '.'),
|
||||||
('../../README.md', '.'),
|
('../../README.md', '.'),
|
||||||
('../../LICENSE', '.'),
|
('../../LICENSE', '.'),
|
||||||
('../../.version', '.'),
|
('../../version.yml', '.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
import_these = []
|
import_these = [
|
||||||
|
'numpy.core._dtype_ctypes' # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||||
|
]
|
||||||
|
|
||||||
# Walk directories that do dynamic importing
|
# Walk directories that do dynamic importing
|
||||||
paths = ('eos/effects', 'eos/db/migrations', 'service/conversions')
|
paths = ('eos/effects', 'eos/db/migrations', 'service/conversions')
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ items_table = Table("invtypes", gamedata_meta,
|
|||||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||||
Column("iconID", Integer),
|
Column("iconID", Integer),
|
||||||
Column("graphicID", Integer),
|
Column("graphicID", Integer),
|
||||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
|
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
||||||
|
Column("replaceSame", String),
|
||||||
|
Column("replaceBetter", String))
|
||||||
|
|
||||||
from .metaGroup import metatypes_table # noqa
|
from .metaGroup import metatypes_table # noqa
|
||||||
from .traits import traits_table # noqa
|
from .traits import traits_table # noqa
|
||||||
|
|||||||
18
eos/db/migrations/upgrade29.py
Normal file
18
eos/db/migrations/upgrade29.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
Migration 29
|
||||||
|
|
||||||
|
- adds spoolType and spoolAmount to modules table
|
||||||
|
"""
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT spoolType FROM modules LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN spoolType INT;")
|
||||||
|
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT spoolAmount FROM modules LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN spoolAmount FLOAT;")
|
||||||
17
eos/db/migrations/upgrade30.py
Normal file
17
eos/db/migrations/upgrade30.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Migration 30
|
||||||
|
|
||||||
|
- changes to prices table
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(saveddata_engine):
|
||||||
|
try:
|
||||||
|
saveddata_engine.execute("SELECT status FROM prices LIMIT 1")
|
||||||
|
except sqlalchemy.exc.DatabaseError:
|
||||||
|
# Just drop table, table will be re-created by sqlalchemy and
|
||||||
|
# data will be re-fetched
|
||||||
|
saveddata_engine.execute("DROP TABLE prices;")
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
|
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, CheckConstraint, Boolean, DateTime
|
||||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||||
from sqlalchemy.orm import relation, mapper
|
from sqlalchemy.orm import relation, mapper
|
||||||
import datetime
|
import datetime
|
||||||
@@ -40,6 +40,8 @@ modules_table = Table("modules", saveddata_meta,
|
|||||||
Column("position", Integer),
|
Column("position", Integer),
|
||||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||||
|
Column("spoolType", Integer, nullable=True),
|
||||||
|
Column("spoolAmount", Float, nullable=True),
|
||||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||||
|
|
||||||
mapper(Module, modules_table,
|
mapper(Module, modules_table,
|
||||||
|
|||||||
@@ -17,17 +17,20 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
|
|
||||||
from sqlalchemy import Table, Column, Float, Integer
|
from sqlalchemy import Table, Column, Float, Integer
|
||||||
from sqlalchemy.orm import mapper
|
from sqlalchemy.orm import mapper
|
||||||
|
|
||||||
from eos.db import saveddata_meta
|
from eos.db import saveddata_meta
|
||||||
from eos.saveddata.price import Price
|
from eos.saveddata.price import Price
|
||||||
|
|
||||||
|
|
||||||
prices_table = Table("prices", saveddata_meta,
|
prices_table = Table("prices", saveddata_meta,
|
||||||
Column("typeID", Integer, primary_key=True),
|
Column("typeID", Integer, primary_key=True),
|
||||||
Column("price", Float, default=0.0),
|
Column("price", Float, default=0.0),
|
||||||
Column("time", Integer, nullable=False),
|
Column("time", Integer, nullable=False),
|
||||||
Column("failed", Integer))
|
Column("status", Integer, nullable=False))
|
||||||
|
|
||||||
|
|
||||||
mapper(Price, prices_table, properties={
|
mapper(Price, prices_table, properties={
|
||||||
"_Price__price": prices_table.c.price,
|
"_Price__price": prices_table.c.price,
|
||||||
|
|||||||
@@ -542,8 +542,17 @@ def commit():
|
|||||||
with sd_lock:
|
with sd_lock:
|
||||||
try:
|
try:
|
||||||
saveddata_session.commit()
|
saveddata_session.commit()
|
||||||
saveddata_session.flush()
|
except Exception:
|
||||||
except Exception as ex:
|
saveddata_session.rollback()
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||||
|
|
||||||
|
|
||||||
|
def flush():
|
||||||
|
with sd_lock:
|
||||||
|
try:
|
||||||
|
saveddata_session.flush()
|
||||||
|
except Exception:
|
||||||
saveddata_session.rollback()
|
saveddata_session.rollback()
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||||
|
|||||||
@@ -141,6 +141,23 @@ class HandledModuleList(HandledList):
|
|||||||
self.remove(mod)
|
self.remove(mod)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def replaceRackPosition(self, rackPosition, mod):
|
||||||
|
listPositions = []
|
||||||
|
for currMod in self:
|
||||||
|
if currMod.slot == mod.slot:
|
||||||
|
listPositions.append(currMod.position)
|
||||||
|
listPositions.sort()
|
||||||
|
try:
|
||||||
|
modListPosition = listPositions[rackPosition]
|
||||||
|
except IndexError:
|
||||||
|
self.appendIgnoreEmpty(mod)
|
||||||
|
else:
|
||||||
|
self.toDummy(modListPosition)
|
||||||
|
if not mod.isEmpty:
|
||||||
|
self.toModule(modListPosition, mod)
|
||||||
|
if mod.isInvalid:
|
||||||
|
self.toDummy(modListPosition)
|
||||||
|
|
||||||
def insert(self, index, mod):
|
def insert(self, index, mod):
|
||||||
mod.position = index
|
mod.position = index
|
||||||
i = index
|
i = index
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ammoInfluenceCapNeed
|
# ammoInfluenceCapNeed
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Items from category: Charge (493 of 947)
|
# Items from category: Charge (493 of 949)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ammoInfluenceRange
|
# ammoInfluenceRange
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Items from category: Charge (587 of 947)
|
# Items from category: Charge (587 of 949)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# ammoSpeedMultiplier
|
# ammoSpeedMultiplier
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Charges from group: Festival Charges (23 of 23)
|
# Charges from group: Festival Charges (26 of 26)
|
||||||
# Charges from group: Interdiction Probe (2 of 2)
|
# Charges from group: Interdiction Probe (2 of 2)
|
||||||
# Charges from group: Structure Festival Charges (3 of 3)
|
# Items from market group: Special Edition Assets > Special Edition Festival Assets (30 of 33)
|
||||||
# Special Edition Assetss from group: Festival Charges Expired (2 of 2)
|
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# ammoTrackingMultiplier
|
# ammoTrackingMultiplier
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Items from category: Charge (182 of 947)
|
# Items from category: Charge (182 of 949)
|
||||||
# Charges from group: Projectile Ammo (128 of 128)
|
# Charges from group: Projectile Ammo (128 of 128)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ type = "active"
|
|||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
amount = module.getModifiedItemAttr("armorDamageAmount")
|
amount = module.getModifiedItemAttr("armorDamageAmount")
|
||||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||||
fit.extraAttributes.increase("armorRepair", amount / speed)
|
rps = amount / speed
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# Implants named like: Eifyr and Co. 'Alchemist' Neurotoxin Control NC (2 of 2)
|
# Implants named like: Eifyr and Co. 'Alchemist' Neurotoxin Control NC (2 of 2)
|
||||||
# Implants named like: grade Edge (10 of 12)
|
# Implants named like: grade Edge (10 of 12)
|
||||||
# Skill: Neurotoxin Control
|
# Skill: Neurotoxin Control
|
||||||
|
runTime = 'early'
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# boosterShieldCapacityPenalty
|
# boosterShieldCapacityPenalty
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants from group: Booster (12 of 69)
|
# Implants from group: Booster (12 of 70)
|
||||||
type = "boosterSideEffect"
|
type = "boosterSideEffect"
|
||||||
|
|
||||||
# User-friendly name for the side effect
|
# User-friendly name for the side effect
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, ship, context):
|
def handler(fit, ship, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
|
||||||
"duration", ship.getModifiedItemAttr("durationBonus"))
|
"duration", ship.getModifiedItemAttr("durationBonus"))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# cynosuralGeneration
|
# cynosuralGeneration
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Cynosural Field (2 of 2)
|
# Modules from group: Cynosural Field Generator (2 of 2)
|
||||||
type = "active"
|
type = "active"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ type = "passive"
|
|||||||
|
|
||||||
def handler(fit, container, context):
|
def handler(fit, container, context):
|
||||||
level = container.level if "skill" in context else 1
|
level = container.level if "skill" in context else 1
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
|
||||||
"consumptionQuantity",
|
"consumptionQuantity",
|
||||||
container.getModifiedItemAttr("consumptionQuantityBonusPercentage") * level)
|
container.getModifiedItemAttr("consumptionQuantityBonusPercentage") * level)
|
||||||
|
|||||||
@@ -11,6 +11,5 @@ def handler(fit, module, context):
|
|||||||
bonus = "%s%sDamageResonance" % (attrPrefix, damageType)
|
bonus = "%s%sDamageResonance" % (attrPrefix, damageType)
|
||||||
bonus = "%s%s" % (bonus[0].lower(), bonus[1:])
|
bonus = "%s%s" % (bonus[0].lower(), bonus[1:])
|
||||||
booster = "%s%sDamageResonance" % (layer, damageType)
|
booster = "%s%sDamageResonance" % (layer, damageType)
|
||||||
penalize = False if layer == 'hull' else True
|
|
||||||
fit.ship.multiplyItemAttr(bonus, module.getModifiedItemAttr(booster),
|
fit.ship.multiplyItemAttr(bonus, module.getModifiedItemAttr(booster),
|
||||||
stackingPenalties=penalize, penaltyGroup="preMul")
|
stackingPenalties=True, penaltyGroup="preMul")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# doHacking
|
# doHacking
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Data Miners (9 of 9)
|
# Modules from group: Data Miners (10 of 10)
|
||||||
type = "active"
|
type = "active"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# droneArmorDamageBonusEffect
|
# droneArmorDamageBonusEffect
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (5 of 6)
|
# Ships from group: Logistics (6 of 7)
|
||||||
# Ship: Exequror
|
# Ship: Exequror
|
||||||
# Ship: Scythe
|
# Ship: Scythe
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# droneHullRepairBonusEffect
|
# droneHullRepairBonusEffect
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (5 of 6)
|
# Ships from group: Logistics (6 of 7)
|
||||||
# Ship: Exequror
|
# Ship: Exequror
|
||||||
# Ship: Scythe
|
# Ship: Scythe
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# droneShieldBonusBonusEffect
|
# droneShieldBonusBonusEffect
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (5 of 6)
|
# Ships from group: Logistics (6 of 7)
|
||||||
# Ship: Exequror
|
# Ship: Exequror
|
||||||
# Ship: Scythe
|
# Ship: Scythe
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ runtime = "late"
|
|||||||
|
|
||||||
def handler(fit, src, context):
|
def handler(fit, src, context):
|
||||||
for dmgType in ('em', 'thermal', 'kinetic', 'explosive'):
|
for dmgType in ('em', 'thermal', 'kinetic', 'explosive'):
|
||||||
fit.ship.forceItemAttr('{}DamageResonance'.format(dmgType), src.getModifiedItemAttr("hull{}DamageResonance".format(dmgType.title())))
|
fit.ship.multiplyItemAttr('{}DamageResonance'.format(dmgType),
|
||||||
|
src.getModifiedItemAttr("hull{}DamageResonance".format(dmgType.title())),
|
||||||
|
stackingPenalties=True, penaltyGroup="postMul")
|
||||||
|
|||||||
@@ -14,4 +14,7 @@ def handler(fit, module, context):
|
|||||||
|
|
||||||
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
||||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||||
fit.extraAttributes.increase("armorRepair", amount / speed)
|
rps = amount / speed
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Variations of module: Ice Harvester Upgrade I (5 of 5)
|
# Variations of module: Ice Harvester Upgrade I (5 of 5)
|
||||||
|
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# implantWarpScrambleRangeBonus
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Warp Scrambler", "maxRange",
|
||||||
|
src.getModifiedItemAttr("warpScrambleRangeBonus"), stackingPenalties=False)
|
||||||
@@ -6,4 +6,4 @@ type = "passive"
|
|||||||
|
|
||||||
|
|
||||||
def handler(fit, module, context):
|
def handler(fit, module, context):
|
||||||
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"))
|
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"), stackingPenalties=True)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||||
|
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
10
eos/effects/miningdurationmultiplieronline.py
Normal file
10
eos/effects/miningdurationmultiplieronline.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# miningDurationMultiplierOnline
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mining Laser",
|
||||||
|
"duration", module.getModifiedItemAttr("miningDurationMultiplier"))
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||||
|
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||||
|
# Module: ML-EKP 'Polybolos' Ballistic Control System
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileDMGBonus
|
# missileDMGBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Ballistic Control system (21 of 21)
|
# Modules from group: Ballistic Control system (22 of 22)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# missileLauncherSpeedMultiplier
|
# missileLauncherSpeedMultiplier
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Ballistic Control system (21 of 21)
|
# Modules from group: Ballistic Control system (22 of 22)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,7 @@ def handler(fit, container, context):
|
|||||||
if "projected" in context:
|
if "projected" in context:
|
||||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||||
duration = container.getModifiedItemAttr("duration") / 1000.0
|
duration = container.getModifiedItemAttr("duration") / 1000.0
|
||||||
fit.extraAttributes.increase("armorRepair", bonus / duration)
|
rps = bonus / duration
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Missile Launcher Torpedo (22 of 22)
|
# Modules from group: Missile Launcher Torpedo (22 of 22)
|
||||||
# Items from market group: Ship Equipment > Turrets & Bays (429 of 881)
|
# Items from market group: Ship Equipment > Turrets & Bays (429 of 883)
|
||||||
# Module: Interdiction Sphere Launcher I
|
# Module: Interdiction Sphere Launcher I
|
||||||
type = "overheat"
|
type = "overheat"
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# overloadSelfDurationBonus
|
# overloadSelfDurationBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# 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 (59 of 59)
|
||||||
# Modules from group: Energy Neutralizer (54 of 54)
|
# Modules from group: Energy Neutralizer (54 of 54)
|
||||||
# Modules from group: Energy Nosferatu (54 of 54)
|
# Modules from group: Energy Nosferatu (54 of 54)
|
||||||
# Modules from group: Hull Repair Unit (25 of 25)
|
# Modules from group: Hull Repair Unit (25 of 25)
|
||||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
|
||||||
# Modules from group: Remote Shield Booster (38 of 38)
|
# Modules from group: Remote Shield Booster (38 of 38)
|
||||||
# Modules from group: Smart Bomb (118 of 118)
|
# Modules from group: Smart Bomb (118 of 118)
|
||||||
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||||
# Modules named like: Ancillary Remote (8 of 8)
|
# Modules named like: Remote Repairer (56 of 56)
|
||||||
# Module: Reactive Armor Hardener
|
# Module: Reactive Armor Hardener
|
||||||
# Module: Target Spectrum Breaker
|
# Module: Target Spectrum Breaker
|
||||||
type = "overheat"
|
type = "overheat"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# remoteCapacitorTransmitterPowerNeedBonusEffect
|
# remoteCapacitorTransmitterPowerNeedBonusEffect
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (3 of 6)
|
# Ships from group: Logistics (3 of 7)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
|
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
|
||||||
|
# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# shieldTransportCpuNeedBonusEffect
|
# shieldTransportCpuNeedBonusEffect
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (3 of 6)
|
# Ships from group: Logistics (3 of 7)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRemoteRepairRangeRole3
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Rodiva
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "maxRange", src.getModifiedItemAttr("shipBonusRole3"))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRemoteRepAmounteliteBonusLogisitics2
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Zarmazd
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "armorDamageAmount", src.getModifiedItemAttr("eliteBonusLogistics2"), skill="Logistics Cruisers")
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRemoteRepCapNeedeliteBonusLogisitics1
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Zarmazd
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "capacitorNeed", src.getModifiedItemAttr("eliteBonusLogistics1"), skill="Logistics Cruisers")
|
||||||
7
eos/effects/shipbonusmutadaptiveremotereprangepc1.py
Normal file
7
eos/effects/shipbonusmutadaptiveremotereprangepc1.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRemoteRepRangePC1
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Zarmazd
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "maxRange", src.getModifiedItemAttr("shipBonusPC1"), skill="Precursor Cruiser")
|
||||||
7
eos/effects/shipbonusmutadaptiverepamountpc1.py
Normal file
7
eos/effects/shipbonusmutadaptiverepamountpc1.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRepAmountPC1
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Rodiva
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "armorDamageAmount", src.getModifiedItemAttr("shipBonusPC1"), skill="Precursor Cruiser")
|
||||||
7
eos/effects/shipbonusmutadaptiverepcapneedpc2.py
Normal file
7
eos/effects/shipbonusmutadaptiverepcapneedpc2.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusMutadaptiveRepCapNeedPC2
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Ship: Rodiva
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "capacitorNeed", src.getModifiedItemAttr("shipBonusPC2"), skill="Precursor Cruiser")
|
||||||
7
eos/effects/shipbonusnosneutcapneedrolebonus2.py
Normal file
7
eos/effects/shipbonusnosneutcapneedrolebonus2.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusNosNeutCapNeedRoleBonus2
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Variations of ship: Rodiva (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capacitor Emission Systems"), "capacitorNeed", src.getModifiedItemAttr("shipBonusRole2"))
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# shipBonusRemoteCapacitorTransferRangeRole1
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Variations of ship: Rodiva (2 of 2)
|
||||||
|
type = "passive"
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Remote Capacitor Transmitter", "maxRange", src.getModifiedItemAttr("shipBonusRole1"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# shipBonusRole5RemoteArmorRepairPowergridBonus
|
# shipBonusRole5RemoteArmorRepairPowergridBonus
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Ships from group: Logistics (3 of 6)
|
# Ships from group: Logistics (3 of 7)
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# shipBonusSmartbombCapNeedRoleBonus2
|
# shipBonusSmartbombCapNeedRoleBonus2
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
|
# Variations of ship: Rodiva (2 of 2)
|
||||||
# Ship: Damavik
|
# Ship: Damavik
|
||||||
# Ship: Drekavac
|
# Ship: Drekavac
|
||||||
# Ship: Hydra
|
# Ship: Hydra
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Ancillary Remote Armor Repairer (4 of 4)
|
# Modules from group: Ancillary Remote Armor Repairer (4 of 4)
|
||||||
runTime = "late"
|
|
||||||
type = "projected", "active"
|
type = "projected", "active"
|
||||||
|
runTime = "late"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, module, context, **kwargs):
|
def handler(fit, module, context, **kwargs):
|
||||||
@@ -17,4 +18,7 @@ def handler(fit, module, context, **kwargs):
|
|||||||
|
|
||||||
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
||||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||||
fit.extraAttributes.increase("armorRepair", amount / speed, **kwargs)
|
rps = amount / speed
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||||
runTime = "late"
|
|
||||||
type = "projected", "active"
|
type = "projected", "active"
|
||||||
|
runTime = "late"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, module, context, **kwargs):
|
def handler(fit, module, context, **kwargs):
|
||||||
|
|||||||
28
eos/effects/shipmoduleremotearmormutadaptiverepairer.py
Normal file
28
eos/effects/shipmoduleremotearmormutadaptiverepairer.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# ShipModuleRemoteArmorMutadaptiveRepairer
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Modules from group: Mutadaptive Remote Armor Repairer (5 of 5)
|
||||||
|
|
||||||
|
|
||||||
|
from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, resolveSpoolOptions
|
||||||
|
|
||||||
|
|
||||||
|
type = "projected", "active"
|
||||||
|
runTime = "late"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, container, context, **kwargs):
|
||||||
|
if "projected" in context:
|
||||||
|
repAmountBase = container.getModifiedItemAttr("armorDamageAmount")
|
||||||
|
cycleTime = container.getModifiedItemAttr("duration") / 1000.0
|
||||||
|
repSpoolMax = container.getModifiedItemAttr("repairMultiplierBonusMax")
|
||||||
|
repSpoolPerCycle = container.getModifiedItemAttr("repairMultiplierBonusPerCycle")
|
||||||
|
# TODO: fetch spoolup option
|
||||||
|
defaultSpoolValue = 1
|
||||||
|
spoolType, spoolAmount = resolveSpoolOptions(SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False), container)
|
||||||
|
rps = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, spoolType, spoolAmount)[0]) / cycleTime
|
||||||
|
rpsPreSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 0)[0]) / cycleTime
|
||||||
|
rpsFullSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 1)[0]) / cycleTime
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps, **kwargs)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rpsPreSpool, **kwargs)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rpsFullSpool, **kwargs)
|
||||||
@@ -2,11 +2,16 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||||
|
|
||||||
type = "projected", "active"
|
type = "projected", "active"
|
||||||
|
runTime = "late"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, container, context, **kwargs):
|
def handler(fit, container, context, **kwargs):
|
||||||
if "projected" in context:
|
if "projected" in context:
|
||||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||||
duration = container.getModifiedItemAttr("duration") / 1000.0
|
duration = container.getModifiedItemAttr("duration") / 1000.0
|
||||||
fit.extraAttributes.increase("armorRepair", bonus / duration, **kwargs)
|
rps = bonus / duration
|
||||||
|
fit.extraAttributes.increase("armorRepair", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||||
|
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||||
|
|||||||
@@ -2,8 +2,13 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||||
|
|
||||||
|
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||||
|
|
||||||
|
|
||||||
type = "projected", "active"
|
type = "projected", "active"
|
||||||
|
runTime = "late"
|
||||||
|
|
||||||
|
|
||||||
def handler(fit, src, context, **kwargs):
|
def handler(fit, src, context, **kwargs):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
# Modules from group: Remote Hull Repairer (8 of 8)
|
||||||
|
|
||||||
type = "projected", "active"
|
type = "projected", "active"
|
||||||
runTime = "late"
|
runTime = "late"
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ type = "passive"
|
|||||||
|
|
||||||
def handler(fit, skill, context):
|
def handler(fit, skill, context):
|
||||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Missile Launcher Bomb",
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Missile Launcher Bomb",
|
||||||
"moduleReactivationDelay", skill.getModifiedItemAttr("rofBonus") * skill.level)
|
"moduleReactivationDelay", skill.getModifiedItemAttr("reactivationDelayBonus") * skill.level)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# skillBonusDroneDurability
|
# skillBonusDroneDurability
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implants from group: Cyber Drones (4 of 4)
|
|
||||||
# Skill: Drone Durability
|
# Skill: Drone Durability
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# skillBonusDroneDurabilityNotFighters
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Implants from group: Cyber Drones (4 of 4)
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "hp",
|
||||||
|
src.getModifiedItemAttr("hullHpBonus"))
|
||||||
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
|
||||||
|
src.getModifiedItemAttr("armorHpBonus"))
|
||||||
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "shieldCapacity",
|
||||||
|
src.getModifiedItemAttr("shieldCapacityBonus"))
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
# skillBonusDroneInterfacing
|
# skillBonusDroneInterfacing
|
||||||
#
|
#
|
||||||
# Used by:
|
# Used by:
|
||||||
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
|
|
||||||
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
|
|
||||||
# Skill: Drone Interfacing
|
# Skill: Drone Interfacing
|
||||||
type = "passive"
|
type = "passive"
|
||||||
|
|
||||||
|
|||||||
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# skillBonusDroneInterfacingNotFighters
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
|
||||||
|
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, src, context):
|
||||||
|
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "damageMultiplier",
|
||||||
|
src.getModifiedItemAttr("damageMultiplierBonus"))
|
||||||
10
eos/effects/stripminerdurationmultiplier.py
Normal file
10
eos/effects/stripminerdurationmultiplier.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# stripMinerDurationMultiplier
|
||||||
|
#
|
||||||
|
# Used by:
|
||||||
|
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||||
|
type = "passive"
|
||||||
|
|
||||||
|
|
||||||
|
def handler(fit, module, context):
|
||||||
|
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Strip Miner",
|
||||||
|
"duration", module.getModifiedItemAttr("miningDurationMultiplier"))
|
||||||
@@ -239,7 +239,7 @@ class Item(EqBase):
|
|||||||
self.__offensive = None
|
self.__offensive = None
|
||||||
self.__assistive = None
|
self.__assistive = None
|
||||||
self.__overrides = None
|
self.__overrides = None
|
||||||
self.__price = None
|
self.__priceObj = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def attributes(self):
|
def attributes(self):
|
||||||
@@ -446,34 +446,33 @@ class Item(EqBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def price(self):
|
def price(self):
|
||||||
|
|
||||||
# todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8)
|
# todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8)
|
||||||
if self.__price is not None and getattr(self.__price, '_sa_instance_state', None) and self.__price._sa_instance_state.deleted:
|
if self.__priceObj is not None and getattr(self.__priceObj, '_sa_instance_state', None) and self.__priceObj._sa_instance_state.deleted:
|
||||||
pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID))
|
pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID))
|
||||||
self.__price = None
|
self.__priceObj = None
|
||||||
|
|
||||||
if self.__price is None:
|
if self.__priceObj is None:
|
||||||
db_price = eos.db.getPrice(self.ID)
|
db_price = eos.db.getPrice(self.ID)
|
||||||
# do not yet have a price in the database for this item, create one
|
# do not yet have a price in the database for this item, create one
|
||||||
if db_price is None:
|
if db_price is None:
|
||||||
pyfalog.debug("Creating a price for {}".format(self.ID))
|
pyfalog.debug("Creating a price for {}".format(self.ID))
|
||||||
self.__price = types_Price(self.ID)
|
self.__priceObj = types_Price(self.ID)
|
||||||
eos.db.add(self.__price)
|
eos.db.add(self.__priceObj)
|
||||||
eos.db.commit()
|
eos.db.flush()
|
||||||
else:
|
else:
|
||||||
self.__price = db_price
|
self.__priceObj = db_price
|
||||||
|
|
||||||
return self.__price
|
return self.__priceObj
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isAbyssal(self):
|
def isAbyssal(self):
|
||||||
if Item.ABYSSAL_TYPES is None:
|
if Item.ABYSSAL_TYPES is None:
|
||||||
Item.getAbyssalYypes()
|
Item.getAbyssalTypes()
|
||||||
|
|
||||||
return self.ID in Item.ABYSSAL_TYPES
|
return self.ID in Item.ABYSSAL_TYPES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getAbyssalYypes(cls):
|
def getAbyssalTypes(cls):
|
||||||
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class FitDpsGraph(Graph):
|
|||||||
pyfalog.critical(e)
|
pyfalog.critical(e)
|
||||||
|
|
||||||
for mod in fit.modules:
|
for mod in fit.modules:
|
||||||
dps, _ = mod.damageStats(fit.targetResists)
|
dps = mod.getDps(targetResists=fit.targetResists).total
|
||||||
if mod.hardpoint == Hardpoint.TURRET:
|
if mod.hardpoint == Hardpoint.TURRET:
|
||||||
if mod.state >= State.ACTIVE:
|
if mod.state >= State.ACTIVE:
|
||||||
total += dps * self.calculateTurretMultiplier(mod, data)
|
total += dps * self.calculateTurretMultiplier(mod, data)
|
||||||
@@ -88,7 +88,7 @@ class FitDpsGraph(Graph):
|
|||||||
for drone in fit.drones:
|
for drone in fit.drones:
|
||||||
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
|
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
|
||||||
drone, data)
|
drone, data)
|
||||||
dps, _ = drone.damageStats(fit.targetResists)
|
dps = drone.getDps(targetResists=fit.targetResists).total
|
||||||
total += dps * multiplier
|
total += dps * multiplier
|
||||||
|
|
||||||
# this is janky as fuck
|
# this is janky as fuck
|
||||||
@@ -98,7 +98,7 @@ class FitDpsGraph(Graph):
|
|||||||
for ability in fighter.abilities:
|
for ability in fighter.abilities:
|
||||||
if ability.dealsDamage and ability.active:
|
if ability.dealsDamage and ability.active:
|
||||||
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
||||||
dps, _ = ability.damageStats(fit.targetResists)
|
dps = ability.getDps(targetResists=fit.targetResists).total
|
||||||
total += dps * multiplier
|
total += dps * multiplier
|
||||||
|
|
||||||
return total
|
return total
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
if force is not None:
|
if force is not None:
|
||||||
if cappingValue is not None:
|
if cappingValue is not None:
|
||||||
force = min(force, cappingValue)
|
force = min(force, cappingValue)
|
||||||
|
if key in (50, 30, 48, 11):
|
||||||
|
force = round(force, 2)
|
||||||
return force
|
return force
|
||||||
# Grab our values if they're there, otherwise we'll take default values
|
# Grab our values if they're there, otherwise we'll take default values
|
||||||
preIncrease = self.__preIncreases.get(key, 0)
|
preIncrease = self.__preIncreases.get(key, 0)
|
||||||
@@ -268,7 +270,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
|||||||
# Cap value if we have cap defined
|
# Cap value if we have cap defined
|
||||||
if cappingValue is not None:
|
if cappingValue is not None:
|
||||||
val = min(val, cappingValue)
|
val = min(val, cappingValue)
|
||||||
|
if key in (50, 30, 48, 11):
|
||||||
|
val = round(val, 2)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def __handleSkill(self, skillName):
|
def __handleSkill(self, skillName):
|
||||||
|
|||||||
@@ -42,13 +42,18 @@ class DamagePattern(object):
|
|||||||
return ehp
|
return ehp
|
||||||
|
|
||||||
def calculateEffectiveTank(self, fit, tankInfo):
|
def calculateEffectiveTank(self, fit, tankInfo):
|
||||||
ehps = {}
|
typeMap = {
|
||||||
passiveShield = fit.calculateShieldRecharge()
|
"passiveShield": "shield",
|
||||||
ehps["passiveShield"] = self.effectivify(fit, passiveShield, "shield")
|
"shieldRepair": "shield",
|
||||||
for type in ("shield", "armor", "hull"):
|
"armorRepair": "armor",
|
||||||
ehps["%sRepair" % type] = self.effectivify(fit, tankInfo["%sRepair" % type], type)
|
"armorRepairPreSpool": "armor",
|
||||||
|
"armorRepairFullSpool": "armor",
|
||||||
return ehps
|
"hullRepair": "hull"}
|
||||||
|
ereps = {}
|
||||||
|
for field in tankInfo:
|
||||||
|
if field in typeMap:
|
||||||
|
ereps[field] = self.effectivify(fit, tankInfo[field], typeMap[field])
|
||||||
|
return ereps
|
||||||
|
|
||||||
def effectivify(self, fit, amount, type):
|
def effectivify(self, fit, amount, type):
|
||||||
type = type if type != "hull" else ""
|
type = type if type != "hull" else ""
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ from sqlalchemy.orm import validates, reconstructor
|
|||||||
import eos.db
|
import eos.db
|
||||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
|
||||||
MINING_ATTRIBUTES = ("miningAmount",)
|
MINING_ATTRIBUTES = ("miningAmount",)
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
@@ -65,8 +66,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def build(self):
|
def build(self):
|
||||||
""" Build object. Assumes proper and valid item already set """
|
""" Build object. Assumes proper and valid item already set """
|
||||||
self.__charge = None
|
self.__charge = None
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
self.__volley = None
|
self.__baseRemoteReps = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||||
@@ -120,37 +121,67 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def hasAmmo(self):
|
def hasAmmo(self):
|
||||||
return self.charge is not None
|
return self.charge is not None
|
||||||
|
|
||||||
@property
|
def getVolley(self, targetResists=None):
|
||||||
def dps(self):
|
if not self.dealsDamage or self.amountActive <= 0:
|
||||||
return self.damageStats()
|
return DmgTypes(0, 0, 0, 0)
|
||||||
|
if self.__baseVolley is None:
|
||||||
|
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
|
||||||
|
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
|
||||||
|
self.__baseVolley = DmgTypes(
|
||||||
|
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||||
|
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||||
|
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||||
|
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||||
|
volley = DmgTypes(
|
||||||
|
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||||
|
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||||
|
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||||
|
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||||
|
return volley
|
||||||
|
|
||||||
|
def getDps(self, targetResists=None):
|
||||||
|
volley = self.getVolley(targetResists=targetResists)
|
||||||
|
if not volley:
|
||||||
|
return DmgTypes(0, 0, 0, 0)
|
||||||
|
cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed"
|
||||||
|
cycleTime = self.getModifiedItemAttr(cycleAttr)
|
||||||
|
dpsFactor = 1 / (cycleTime / 1000)
|
||||||
|
dps = DmgTypes(
|
||||||
|
em=volley.em * dpsFactor,
|
||||||
|
thermal=volley.thermal * dpsFactor,
|
||||||
|
kinetic=volley.kinetic * dpsFactor,
|
||||||
|
explosive=volley.explosive * dpsFactor)
|
||||||
|
return dps
|
||||||
|
|
||||||
|
def getRemoteReps(self, ignoreState=False):
|
||||||
|
if self.amountActive <= 0 and not ignoreState:
|
||||||
|
return (None, 0)
|
||||||
|
if self.__baseRemoteReps is None:
|
||||||
|
rrShield = self.getModifiedItemAttr("shieldBonus", 0)
|
||||||
|
rrArmor = self.getModifiedItemAttr("armorDamageAmount", 0)
|
||||||
|
rrHull = self.getModifiedItemAttr("structureDamageAmount", 0)
|
||||||
|
if rrShield:
|
||||||
|
rrType = "Shield"
|
||||||
|
rrAmount = rrShield
|
||||||
|
elif rrArmor:
|
||||||
|
rrType = "Armor"
|
||||||
|
rrAmount = rrArmor
|
||||||
|
elif rrHull:
|
||||||
|
rrType = "Hull"
|
||||||
|
rrAmount = rrHull
|
||||||
|
else:
|
||||||
|
rrType = None
|
||||||
|
rrAmount = 0
|
||||||
|
if rrAmount:
|
||||||
|
droneAmount = self.amount if ignoreState else self.amountActive
|
||||||
|
rrAmount *= droneAmount / (self.cycleTime / 1000)
|
||||||
|
self.__baseRemoteReps = (rrType, rrAmount)
|
||||||
|
return self.__baseRemoteReps
|
||||||
|
|
||||||
def changeType(self, typeID):
|
def changeType(self, typeID):
|
||||||
self.itemID = typeID
|
self.itemID = typeID
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
def damageStats(self, targetResists=None):
|
|
||||||
if self.__dps is None:
|
|
||||||
self.__volley = 0
|
|
||||||
self.__dps = 0
|
|
||||||
if self.dealsDamage is True and self.amountActive > 0:
|
|
||||||
if self.hasAmmo:
|
|
||||||
attr = "missileLaunchDuration"
|
|
||||||
getter = self.getModifiedChargeAttr
|
|
||||||
else:
|
|
||||||
attr = "speed"
|
|
||||||
getter = self.getModifiedItemAttr
|
|
||||||
|
|
||||||
cycleTime = self.getModifiedItemAttr(attr)
|
|
||||||
|
|
||||||
volley = sum(
|
|
||||||
[(getter("%sDamage" % d) or 0) * (1 - getattr(targetResists, "%sAmount" % d, 0)) for d in self.DAMAGE_TYPES])
|
|
||||||
volley *= self.amountActive
|
|
||||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
|
||||||
self.__volley = volley
|
|
||||||
self.__dps = volley / (cycleTime / 1000.0)
|
|
||||||
|
|
||||||
return self.__dps, self.__volley
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def miningStats(self):
|
def miningStats(self):
|
||||||
if self.__miningyield is None:
|
if self.__miningyield is None:
|
||||||
@@ -208,8 +239,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
self.__volley = None
|
self.__baseRemoteReps = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.itemModifiedAttributes.clear()
|
self.itemModifiedAttributes.clear()
|
||||||
self.chargeModifiedAttributes.clear()
|
self.chargeModifiedAttributes.clear()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
|||||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||||
from eos.saveddata.fighterAbility import FighterAbility
|
from eos.saveddata.fighterAbility import FighterAbility
|
||||||
from eos.saveddata.module import Slot
|
from eos.saveddata.module import Slot
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -87,8 +88,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def build(self):
|
def build(self):
|
||||||
""" Build object. Assumes proper and valid item already set """
|
""" Build object. Assumes proper and valid item already set """
|
||||||
self.__charge = None
|
self.__charge = None
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
self.__volley = None
|
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||||
@@ -172,43 +172,88 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
def hasAmmo(self):
|
def hasAmmo(self):
|
||||||
return self.charge is not None
|
return self.charge is not None
|
||||||
|
|
||||||
@property
|
def getVolley(self, targetResists=None):
|
||||||
def dps(self):
|
if not self.active or self.amountActive <= 0:
|
||||||
return self.damageStats()
|
return DmgTypes(0, 0, 0, 0)
|
||||||
|
if self.__baseVolley is None:
|
||||||
|
em = 0
|
||||||
|
therm = 0
|
||||||
|
kin = 0
|
||||||
|
exp = 0
|
||||||
|
for ability in self.abilities:
|
||||||
|
# Not passing resists here as we want to calculate and store base volley
|
||||||
|
abilityVolley = ability.getVolley()
|
||||||
|
em += abilityVolley.em
|
||||||
|
therm += abilityVolley.thermal
|
||||||
|
kin += abilityVolley.kinetic
|
||||||
|
exp += abilityVolley.explosive
|
||||||
|
self.__baseVolley = DmgTypes(em, therm, kin, exp)
|
||||||
|
volley = DmgTypes(
|
||||||
|
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||||
|
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||||
|
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||||
|
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||||
|
return volley
|
||||||
|
|
||||||
def damageStats(self, targetResists=None):
|
def getDps(self, targetResists=None):
|
||||||
if self.__dps is None:
|
if not self.active or self.amountActive <= 0:
|
||||||
self.__volley = 0
|
return DmgTypes(0, 0, 0, 0)
|
||||||
self.__dps = 0
|
# Analyze cooldowns when reload is factored in
|
||||||
if self.active and self.amountActive > 0:
|
if self.owner.factorReload:
|
||||||
for ability in self.abilities:
|
activeTimes = []
|
||||||
dps, volley = ability.damageStats(targetResists)
|
reloadTimes = []
|
||||||
self.__dps += dps
|
peakEm = 0
|
||||||
self.__volley += volley
|
peakTherm = 0
|
||||||
|
peakKin = 0
|
||||||
# For forward compatability this assumes a fighter
|
peakExp = 0
|
||||||
# can have more than 2 damaging abilities and/or
|
steadyEm = 0
|
||||||
# multiple that use charges.
|
steadyTherm = 0
|
||||||
if self.owner.factorReload:
|
steadyKin = 0
|
||||||
activeTimes = []
|
steadyExp = 0
|
||||||
reloadTimes = []
|
for ability in self.abilities:
|
||||||
constantDps = 0
|
abilityDps = ability.getDps(targetResists=targetResists)
|
||||||
for ability in self.abilities:
|
# Peak dps
|
||||||
if not ability.active:
|
peakEm += abilityDps.em
|
||||||
continue
|
peakTherm += abilityDps.thermal
|
||||||
if ability.numShots == 0:
|
peakKin += abilityDps.kinetic
|
||||||
dps, volley = ability.damageStats(targetResists)
|
peakExp += abilityDps.explosive
|
||||||
constantDps += dps
|
# Infinite use - add to steady dps
|
||||||
continue
|
if ability.numShots == 0:
|
||||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
steadyEm += abilityDps.em
|
||||||
reloadTimes.append(ability.reloadTime)
|
steadyTherm += abilityDps.thermal
|
||||||
|
steadyKin += abilityDps.kinetic
|
||||||
if len(activeTimes) > 0:
|
steadyExp += abilityDps.explosive
|
||||||
shortestActive = sorted(activeTimes)[0]
|
else:
|
||||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||||
self.__dps = max(constantDps, self.__dps * shortestActive / (shortestActive + longestReload))
|
reloadTimes.append(ability.reloadTime)
|
||||||
|
steadyDps = DmgTypes(steadyEm, steadyTherm, steadyKin, steadyExp)
|
||||||
return self.__dps, self.__volley
|
if len(activeTimes) > 0:
|
||||||
|
shortestActive = sorted(activeTimes)[0]
|
||||||
|
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||||
|
peakDps = DmgTypes(peakEm, peakTherm, peakKin, peakExp)
|
||||||
|
peakAdjustFactor = shortestActive / (shortestActive + longestReload)
|
||||||
|
peakDpsAdjusted = DmgTypes(
|
||||||
|
em=peakDps.em * peakAdjustFactor,
|
||||||
|
thermal=peakDps.thermal * peakAdjustFactor,
|
||||||
|
kinetic=peakDps.kinetic * peakAdjustFactor,
|
||||||
|
explosive=peakDps.explosive * peakAdjustFactor)
|
||||||
|
dps = max(steadyDps, peakDpsAdjusted, key=lambda d: d.total)
|
||||||
|
return dps
|
||||||
|
else:
|
||||||
|
return steadyDps
|
||||||
|
# Just sum all abilities when not taking reload into consideration
|
||||||
|
else:
|
||||||
|
em = 0
|
||||||
|
therm = 0
|
||||||
|
kin = 0
|
||||||
|
exp = 0
|
||||||
|
for ability in self.abilities:
|
||||||
|
abilityDps = ability.getDps(targetResists=targetResists)
|
||||||
|
em += abilityDps.em
|
||||||
|
therm += abilityDps.thermal
|
||||||
|
kin += abilityDps.kinetic
|
||||||
|
exp += abilityDps.explosive
|
||||||
|
return DmgTypes(em, therm, kin, exp)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def maxRange(self):
|
def maxRange(self):
|
||||||
@@ -251,8 +296,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
self.__volley = None
|
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.itemModifiedAttributes.clear()
|
self.itemModifiedAttributes.clear()
|
||||||
self.chargeModifiedAttributes.clear()
|
self.chargeModifiedAttributes.clear()
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ from logbook import Logger
|
|||||||
|
|
||||||
from sqlalchemy.orm import reconstructor
|
from sqlalchemy.orm import reconstructor
|
||||||
|
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FighterAbility(object):
|
class FighterAbility(object):
|
||||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
|
||||||
DAMAGE_TYPES2 = ("EM", "Kin", "Exp", "Therm")
|
|
||||||
|
|
||||||
# We aren't able to get data on the charges that can be stored with fighters. So we hardcode that data here, keyed
|
# We aren't able to get data on the charges that can be stored with fighters. So we hardcode that data here, keyed
|
||||||
# with the fighter squadron role
|
# with the fighter squadron role
|
||||||
@@ -118,30 +118,38 @@ class FighterAbility(object):
|
|||||||
|
|
||||||
return speed
|
return speed
|
||||||
|
|
||||||
def damageStats(self, targetResists=None):
|
def getVolley(self, targetResists=None):
|
||||||
if self.__dps is None:
|
if not self.dealsDamage or not self.active:
|
||||||
self.__volley = 0
|
return DmgTypes(0, 0, 0, 0)
|
||||||
self.__dps = 0
|
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||||
if self.dealsDamage and self.active:
|
em = self.fighter.getModifiedChargeAttr("emDamage", 0)
|
||||||
cycleTime = self.cycleTime
|
therm = self.fighter.getModifiedChargeAttr("thermalDamage", 0)
|
||||||
|
kin = self.fighter.getModifiedChargeAttr("kineticDamage", 0)
|
||||||
|
exp = self.fighter.getModifiedChargeAttr("explosiveDamage", 0)
|
||||||
|
else:
|
||||||
|
em = self.fighter.getModifiedItemAttr("{}DamageEM".format(self.attrPrefix), 0)
|
||||||
|
therm = self.fighter.getModifiedItemAttr("{}DamageTherm".format(self.attrPrefix), 0)
|
||||||
|
kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix), 0)
|
||||||
|
exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix), 0)
|
||||||
|
dmgMult = self.fighter.amountActive * self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix), 1)
|
||||||
|
volley = DmgTypes(
|
||||||
|
em=em * dmgMult * (1 - getattr(targetResists, "emAmount", 0)),
|
||||||
|
thermal=therm * dmgMult * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||||
|
kinetic=kin * dmgMult * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||||
|
explosive=exp * dmgMult * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||||
|
return volley
|
||||||
|
|
||||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
def getDps(self, targetResists=None):
|
||||||
# bomb calcs
|
volley = self.getVolley(targetResists=targetResists)
|
||||||
volley = sum([(self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * (
|
if not volley:
|
||||||
1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
return DmgTypes(0, 0, 0, 0)
|
||||||
else:
|
dpsFactor = 1 / (self.cycleTime / 1000)
|
||||||
volley = sum(map(lambda d2, d:
|
dps = DmgTypes(
|
||||||
(self.fighter.getModifiedItemAttr(
|
em=volley.em * dpsFactor,
|
||||||
"{}Damage{}".format(self.attrPrefix, d2)) or 0) *
|
thermal=volley.thermal * dpsFactor,
|
||||||
(1 - getattr(targetResists, "{}Amount".format(d), 0)),
|
kinetic=volley.kinetic * dpsFactor,
|
||||||
self.DAMAGE_TYPES2, self.DAMAGE_TYPES))
|
explosive=volley.explosive * dpsFactor)
|
||||||
|
return dps
|
||||||
volley *= self.fighter.amountActive
|
|
||||||
volley *= self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix)) or 1
|
|
||||||
self.__volley += volley
|
|
||||||
self.__dps += volley / (cycleTime / 1000.0)
|
|
||||||
|
|
||||||
return self.__dps, self.__volley
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.__dps = None
|
self.__dps = None
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from eos.saveddata.drone import Drone
|
|||||||
from eos.saveddata.character import Character
|
from eos.saveddata.character import Character
|
||||||
from eos.saveddata.citadel import Citadel
|
from eos.saveddata.citadel import Citadel
|
||||||
from eos.saveddata.module import Module, State, Slot, Hardpoint
|
from eos.saveddata.module import Module, State, Slot, Hardpoint
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -120,10 +121,11 @@ class Fit(object):
|
|||||||
def build(self):
|
def build(self):
|
||||||
self.__extraDrains = []
|
self.__extraDrains = []
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
self.__weaponDPS = None
|
self.__weaponDpsMap = {}
|
||||||
|
self.__weaponVolleyMap = {}
|
||||||
|
self.__remoteRepMap = {}
|
||||||
self.__minerYield = None
|
self.__minerYield = None
|
||||||
self.__weaponVolley = None
|
self.__droneDps = None
|
||||||
self.__droneDPS = None
|
|
||||||
self.__droneVolley = None
|
self.__droneVolley = None
|
||||||
self.__droneYield = None
|
self.__droneYield = None
|
||||||
self.__sustainableTank = None
|
self.__sustainableTank = None
|
||||||
@@ -135,12 +137,6 @@ class Fit(object):
|
|||||||
self.__capUsed = None
|
self.__capUsed = None
|
||||||
self.__capRecharge = None
|
self.__capRecharge = None
|
||||||
self.__calculatedTargets = []
|
self.__calculatedTargets = []
|
||||||
self.__remoteReps = {
|
|
||||||
"Armor" : None,
|
|
||||||
"Shield" : None,
|
|
||||||
"Hull" : None,
|
|
||||||
"Capacitor": None,
|
|
||||||
}
|
|
||||||
self.factorReload = False
|
self.factorReload = False
|
||||||
self.boostsFits = set()
|
self.boostsFits = set()
|
||||||
self.gangBoosts = None
|
self.gangBoosts = None
|
||||||
@@ -154,9 +150,9 @@ class Fit(object):
|
|||||||
@targetResists.setter
|
@targetResists.setter
|
||||||
def targetResists(self, targetResists):
|
def targetResists(self, targetResists):
|
||||||
self.__targetResists = targetResists
|
self.__targetResists = targetResists
|
||||||
self.__weaponDPS = None
|
self.__weaponDpsMap = {}
|
||||||
self.__weaponVolley = None
|
self.__weaponVolleyMap = {}
|
||||||
self.__droneDPS = None
|
self.__droneDps = None
|
||||||
self.__droneVolley = None
|
self.__droneVolley = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -277,41 +273,31 @@ class Fit(object):
|
|||||||
def projectedFighters(self):
|
def projectedFighters(self):
|
||||||
return self.__projectedFighters
|
return self.__projectedFighters
|
||||||
|
|
||||||
@property
|
def getWeaponDps(self, spoolOptions=None):
|
||||||
def weaponDPS(self):
|
if spoolOptions not in self.__weaponDpsMap:
|
||||||
if self.__weaponDPS is None:
|
self.calculateWeaponDmgStats(spoolOptions)
|
||||||
self.calculateWeaponStats()
|
return self.__weaponDpsMap[spoolOptions]
|
||||||
|
|
||||||
return self.__weaponDPS
|
def getWeaponVolley(self, spoolOptions=None):
|
||||||
|
if spoolOptions not in self.__weaponVolleyMap:
|
||||||
|
self.calculateWeaponDmgStats(spoolOptions)
|
||||||
|
return self.__weaponVolleyMap[spoolOptions]
|
||||||
|
|
||||||
@property
|
def getDroneDps(self):
|
||||||
def weaponVolley(self):
|
if self.__droneDps is None:
|
||||||
if self.__weaponVolley is None:
|
self.calculateDroneDmgStats()
|
||||||
self.calculateWeaponStats()
|
return self.__droneDps
|
||||||
|
|
||||||
return self.__weaponVolley
|
def getDroneVolley(self):
|
||||||
|
|
||||||
@property
|
|
||||||
def droneDPS(self):
|
|
||||||
if self.__droneDPS is None:
|
|
||||||
self.calculateWeaponStats()
|
|
||||||
|
|
||||||
return self.__droneDPS
|
|
||||||
|
|
||||||
@property
|
|
||||||
def droneVolley(self):
|
|
||||||
if self.__droneVolley is None:
|
if self.__droneVolley is None:
|
||||||
self.calculateWeaponStats()
|
self.calculateDroneDmgStats()
|
||||||
|
|
||||||
return self.__droneVolley
|
return self.__droneVolley
|
||||||
|
|
||||||
@property
|
def getTotalDps(self, spoolOptions=None):
|
||||||
def totalDPS(self):
|
return self.getDroneDps() + self.getWeaponDps(spoolOptions=spoolOptions)
|
||||||
return self.droneDPS + self.weaponDPS
|
|
||||||
|
|
||||||
@property
|
def getTotalVolley(self, spoolOptions=None):
|
||||||
def totalVolley(self):
|
return self.getDroneVolley() + self.getWeaponVolley(spoolOptions=spoolOptions)
|
||||||
return self.droneVolley + self.weaponVolley
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def minerYield(self):
|
def minerYield(self):
|
||||||
@@ -409,12 +395,13 @@ class Fit(object):
|
|||||||
|
|
||||||
def clear(self, projected=False, command=False):
|
def clear(self, projected=False, command=False):
|
||||||
self.__effectiveTank = None
|
self.__effectiveTank = None
|
||||||
self.__weaponDPS = None
|
self.__weaponDpsMap = {}
|
||||||
|
self.__weaponVolleyMap = {}
|
||||||
|
self.__remoteRepMap = {}
|
||||||
self.__minerYield = None
|
self.__minerYield = None
|
||||||
self.__weaponVolley = None
|
|
||||||
self.__effectiveSustainableTank = None
|
self.__effectiveSustainableTank = None
|
||||||
self.__sustainableTank = None
|
self.__sustainableTank = None
|
||||||
self.__droneDPS = None
|
self.__droneDps = None
|
||||||
self.__droneVolley = None
|
self.__droneVolley = None
|
||||||
self.__droneYield = None
|
self.__droneYield = None
|
||||||
self.__ehp = None
|
self.__ehp = None
|
||||||
@@ -426,9 +413,6 @@ class Fit(object):
|
|||||||
self.ecmProjectedStr = 1
|
self.ecmProjectedStr = 1
|
||||||
# self.commandBonuses = {}
|
# self.commandBonuses = {}
|
||||||
|
|
||||||
for remoterep_type in self.__remoteReps:
|
|
||||||
self.__remoteReps[remoterep_type] = None
|
|
||||||
|
|
||||||
del self.__calculatedTargets[:]
|
del self.__calculatedTargets[:]
|
||||||
del self.__extraDrains[:]
|
del self.__extraDrains[:]
|
||||||
|
|
||||||
@@ -1032,11 +1016,11 @@ class Fit(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pgUsed(self):
|
def pgUsed(self):
|
||||||
return self.getItemAttrOnlineSum(self.modules, "power")
|
return round(self.getItemAttrOnlineSum(self.modules, "power"), 2)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cpuUsed(self):
|
def cpuUsed(self):
|
||||||
return self.getItemAttrOnlineSum(self.modules, "cpu")
|
return round(self.getItemAttrOnlineSum(self.modules, "cpu"), 2)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def droneBandwidthUsed(self):
|
def droneBandwidthUsed(self):
|
||||||
@@ -1151,149 +1135,6 @@ class Fit(object):
|
|||||||
|
|
||||||
return self.__capRecharge
|
return self.__capRecharge
|
||||||
|
|
||||||
@property
|
|
||||||
def sustainableTank(self):
|
|
||||||
if self.__sustainableTank is None:
|
|
||||||
self.calculateSustainableTank()
|
|
||||||
|
|
||||||
return self.__sustainableTank
|
|
||||||
|
|
||||||
def calculateSustainableTank(self, effective=True):
|
|
||||||
if self.__sustainableTank is None:
|
|
||||||
if self.capStable and not self.factorReload:
|
|
||||||
sustainable = {
|
|
||||||
"armorRepair" : self.extraAttributes["armorRepair"],
|
|
||||||
"shieldRepair": self.extraAttributes["shieldRepair"],
|
|
||||||
"hullRepair" : self.extraAttributes["hullRepair"]
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
sustainable = {}
|
|
||||||
|
|
||||||
repairers = []
|
|
||||||
# Map a repairer type to the attribute it uses
|
|
||||||
groupAttrMap = {
|
|
||||||
"Shield Booster": "shieldBonus",
|
|
||||||
"Ancillary Shield Booster": "shieldBonus",
|
|
||||||
"Remote Shield Booster": "shieldBonus",
|
|
||||||
"Ancillary Remote Shield Booster": "shieldBonus",
|
|
||||||
|
|
||||||
"Armor Repair Unit": "armorDamageAmount",
|
|
||||||
"Ancillary Armor Repairer": "armorDamageAmount",
|
|
||||||
"Remote Armor Repairer": "armorDamageAmount",
|
|
||||||
"Ancillary Remote Armor Repairer": "armorDamageAmount",
|
|
||||||
|
|
||||||
"Hull Repair Unit": "structureDamageAmount",
|
|
||||||
"Remote Hull Repairer": "structureDamageAmount",
|
|
||||||
}
|
|
||||||
# Map repairer type to attribute
|
|
||||||
groupStoreMap = {
|
|
||||||
"Shield Booster": "shieldRepair",
|
|
||||||
"Remote Shield Booster": "shieldRepair",
|
|
||||||
"Ancillary Shield Booster": "shieldRepair",
|
|
||||||
"Ancillary Remote Shield Booster": "shieldRepair",
|
|
||||||
|
|
||||||
"Armor Repair Unit": "armorRepair",
|
|
||||||
"Remote Armor Repairer": "armorRepair",
|
|
||||||
"Ancillary Armor Repairer": "armorRepair",
|
|
||||||
"Ancillary Remote Armor Repairer": "armorRepair",
|
|
||||||
|
|
||||||
"Hull Repair Unit": "hullRepair",
|
|
||||||
"Remote Hull Repairer": "hullRepair",
|
|
||||||
}
|
|
||||||
|
|
||||||
capUsed = self.capUsed
|
|
||||||
for attr in ("shieldRepair", "armorRepair", "hullRepair"):
|
|
||||||
sustainable[attr] = self.extraAttributes[attr]
|
|
||||||
dict = self.extraAttributes.getAfflictions(attr)
|
|
||||||
if self in dict:
|
|
||||||
for mod, _, amount, used in dict[self]:
|
|
||||||
if not used:
|
|
||||||
continue
|
|
||||||
if mod.projected is False:
|
|
||||||
usesCap = True
|
|
||||||
try:
|
|
||||||
if mod.capUse:
|
|
||||||
capUsed -= mod.capUse
|
|
||||||
else:
|
|
||||||
usesCap = False
|
|
||||||
except AttributeError:
|
|
||||||
usesCap = False
|
|
||||||
|
|
||||||
# Normal Repairers
|
|
||||||
if usesCap and not mod.charge:
|
|
||||||
cycleTime = mod.rawCycleTime
|
|
||||||
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
|
||||||
sustainable[attr] -= amount / (cycleTime / 1000.0)
|
|
||||||
repairers.append(mod)
|
|
||||||
# Ancillary Armor reps etc
|
|
||||||
elif usesCap and mod.charge:
|
|
||||||
cycleTime = mod.rawCycleTime
|
|
||||||
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
|
||||||
if mod.charge.name == "Nanite Repair Paste":
|
|
||||||
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
|
||||||
else:
|
|
||||||
multiplier = 1
|
|
||||||
sustainable[attr] -= amount * multiplier / (cycleTime / 1000.0)
|
|
||||||
repairers.append(mod)
|
|
||||||
# Ancillary Shield boosters etc
|
|
||||||
elif not usesCap and mod.item.group.name in ("Ancillary Shield Booster", "Ancillary Remote Shield Booster"):
|
|
||||||
cycleTime = mod.rawCycleTime
|
|
||||||
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
|
||||||
if self.factorReload and mod.charge:
|
|
||||||
reloadtime = mod.reloadTime
|
|
||||||
else:
|
|
||||||
reloadtime = 0.0
|
|
||||||
offdutycycle = reloadtime / ((max(mod.numShots, 1) * cycleTime) + reloadtime)
|
|
||||||
sustainable[attr] -= amount * offdutycycle / (cycleTime / 1000.0)
|
|
||||||
|
|
||||||
# Sort repairers by efficiency. We want to use the most efficient repairers first
|
|
||||||
repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr(
|
|
||||||
groupAttrMap[_mod.item.group.name]) * (_mod.getModifiedItemAttr(
|
|
||||||
"chargedArmorDamageMultiplier") or 1) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True)
|
|
||||||
|
|
||||||
# Loop through every module until we're above peak recharge
|
|
||||||
# Most efficient first, as we sorted earlier.
|
|
||||||
# calculate how much the repper can rep stability & add to total
|
|
||||||
totalPeakRecharge = self.capRecharge
|
|
||||||
for mod in repairers:
|
|
||||||
if capUsed > totalPeakRecharge:
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.factorReload and mod.charge:
|
|
||||||
reloadtime = mod.reloadTime
|
|
||||||
else:
|
|
||||||
reloadtime = 0.0
|
|
||||||
|
|
||||||
cycleTime = mod.rawCycleTime
|
|
||||||
capPerSec = mod.capUse
|
|
||||||
|
|
||||||
if capPerSec is not None and cycleTime is not None:
|
|
||||||
# Check how much this repper can work
|
|
||||||
sustainability = min(1, (totalPeakRecharge - capUsed) / capPerSec)
|
|
||||||
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
|
||||||
# Add the sustainable amount
|
|
||||||
|
|
||||||
if not mod.charge:
|
|
||||||
sustainable[groupStoreMap[mod.item.group.name]] += sustainability * amount / (
|
|
||||||
cycleTime / 1000.0)
|
|
||||||
else:
|
|
||||||
if mod.charge.name == "Nanite Repair Paste":
|
|
||||||
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
|
||||||
else:
|
|
||||||
multiplier = 1
|
|
||||||
ondutycycle = (max(mod.numShots, 1) * cycleTime) / (
|
|
||||||
(max(mod.numShots, 1) * cycleTime) + reloadtime)
|
|
||||||
sustainable[groupStoreMap[
|
|
||||||
mod.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
|
|
||||||
cycleTime / 1000.0)
|
|
||||||
|
|
||||||
capUsed += capPerSec
|
|
||||||
|
|
||||||
sustainable["passiveShield"] = self.calculateShieldRecharge()
|
|
||||||
self.__sustainableTank = sustainable
|
|
||||||
|
|
||||||
return self.__sustainableTank
|
|
||||||
|
|
||||||
def calculateCapRecharge(self, percent=PEAK_RECHARGE):
|
def calculateCapRecharge(self, percent=PEAK_RECHARGE):
|
||||||
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
|
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
|
||||||
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
|
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
|
||||||
@@ -1377,92 +1218,27 @@ class Fit(object):
|
|||||||
self.__capStable = True
|
self.__capStable = True
|
||||||
self.__capState = 100
|
self.__capState = 100
|
||||||
|
|
||||||
@property
|
def getRemoteReps(self, spoolOptions=None):
|
||||||
def remoteReps(self):
|
if spoolOptions not in self.__remoteRepMap:
|
||||||
force_recalc = False
|
remoteReps = {}
|
||||||
for remote_type in self.__remoteReps:
|
|
||||||
if self.__remoteReps[remote_type] is None:
|
|
||||||
force_recalc = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if force_recalc is False:
|
for module in self.modules:
|
||||||
return self.__remoteReps
|
rrType, rrAmount = module.getRemoteReps(spoolOptions=spoolOptions)
|
||||||
|
if rrType:
|
||||||
|
if rrType not in remoteReps:
|
||||||
|
remoteReps[rrType] = 0
|
||||||
|
remoteReps[rrType] += rrAmount
|
||||||
|
|
||||||
# We are rerunning the recalcs. Explicitly set to 0 to make sure we don't duplicate anything and correctly set
|
for drone in self.drones:
|
||||||
# all values to 0.
|
rrType, rrAmount = drone.getRemoteReps()
|
||||||
for remote_type in self.__remoteReps:
|
if rrType:
|
||||||
self.__remoteReps[remote_type] = 0
|
if rrType not in remoteReps:
|
||||||
|
remoteReps[rrType] = 0
|
||||||
|
remoteReps[rrType] += rrAmount
|
||||||
|
|
||||||
for stuff in chain(self.modules, self.drones):
|
self.__remoteRepMap[spoolOptions] = remoteReps
|
||||||
if stuff.item:
|
|
||||||
if stuff.item.ID == 10250:
|
|
||||||
pass
|
|
||||||
remote_type = None
|
|
||||||
|
|
||||||
# Only apply the charged multiplier if we have a charge in our ancil reppers (#1135)
|
return self.__remoteRepMap[spoolOptions]
|
||||||
if stuff.charge:
|
|
||||||
modifier = stuff.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
|
||||||
else:
|
|
||||||
modifier = 1
|
|
||||||
|
|
||||||
if isinstance(stuff, Module) and (stuff.isEmpty or stuff.state < State.ACTIVE):
|
|
||||||
continue
|
|
||||||
elif isinstance(stuff, Drone):
|
|
||||||
# drones don't have fueled charges, so simply override modifier with the amount of drones active
|
|
||||||
modifier = stuff.amountActive
|
|
||||||
|
|
||||||
# Covert cycleTime to seconds
|
|
||||||
duration = stuff.cycleTime / 1000
|
|
||||||
|
|
||||||
# Skip modules with no duration.
|
|
||||||
if not duration:
|
|
||||||
continue
|
|
||||||
|
|
||||||
remote_module_groups = {
|
|
||||||
"Remote Armor Repairer" : "Armor",
|
|
||||||
"Ancillary Remote Armor Repairer": "Armor",
|
|
||||||
"Remote Hull Repairer" : "Hull",
|
|
||||||
"Remote Shield Booster" : "Shield",
|
|
||||||
"Ancillary Remote Shield Booster": "Shield",
|
|
||||||
"Remote Capacitor Transmitter" : "Capacitor",
|
|
||||||
}
|
|
||||||
|
|
||||||
module_group = stuff.item.group.name
|
|
||||||
|
|
||||||
if module_group in remote_module_groups:
|
|
||||||
remote_type = remote_module_groups[module_group]
|
|
||||||
elif not isinstance(stuff, Drone):
|
|
||||||
# Module isn't in our list of remote rep modules, bail
|
|
||||||
continue
|
|
||||||
|
|
||||||
if remote_type == "Hull":
|
|
||||||
hp = stuff.getModifiedItemAttr("structureDamageAmount", 0)
|
|
||||||
elif remote_type == "Armor":
|
|
||||||
hp = stuff.getModifiedItemAttr("armorDamageAmount", 0)
|
|
||||||
elif remote_type == "Shield":
|
|
||||||
hp = stuff.getModifiedItemAttr("shieldBonus", 0)
|
|
||||||
elif remote_type == "Capacitor":
|
|
||||||
hp = stuff.getModifiedItemAttr("powerTransferAmount", 0)
|
|
||||||
else:
|
|
||||||
droneShield = stuff.getModifiedItemAttr("shieldBonus", 0)
|
|
||||||
droneArmor = stuff.getModifiedItemAttr("armorDamageAmount", 0)
|
|
||||||
droneHull = stuff.getModifiedItemAttr("structureDamageAmount", 0)
|
|
||||||
if droneShield:
|
|
||||||
remote_type = "Shield"
|
|
||||||
hp = droneShield
|
|
||||||
elif droneArmor:
|
|
||||||
remote_type = "Armor"
|
|
||||||
hp = droneArmor
|
|
||||||
elif droneHull:
|
|
||||||
remote_type = "Hull"
|
|
||||||
hp = droneHull
|
|
||||||
else:
|
|
||||||
hp = 0
|
|
||||||
|
|
||||||
if hp > 0 and duration > 0:
|
|
||||||
self.__remoteReps[remote_type] += (hp * modifier) / duration
|
|
||||||
|
|
||||||
return self.__remoteReps
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hp(self):
|
def hp(self):
|
||||||
@@ -1485,11 +1261,14 @@ class Fit(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def tank(self):
|
def tank(self):
|
||||||
hps = {"passiveShield": self.calculateShieldRecharge()}
|
reps = {
|
||||||
for type in ("shield", "armor", "hull"):
|
"passiveShield": self.calculateShieldRecharge(),
|
||||||
hps["%sRepair" % type] = self.extraAttributes["%sRepair" % type]
|
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||||
|
"armorRepair": self.extraAttributes["armorRepair"],
|
||||||
return hps
|
"armorRepairPreSpool": self.extraAttributes["armorRepairPreSpool"],
|
||||||
|
"armorRepairFullSpool": self.extraAttributes["armorRepairFullSpool"],
|
||||||
|
"hullRepair": self.extraAttributes["hullRepair"]}
|
||||||
|
return reps
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effectiveTank(self):
|
def effectiveTank(self):
|
||||||
@@ -1497,24 +1276,153 @@ class Fit(object):
|
|||||||
if self.damagePattern is None:
|
if self.damagePattern is None:
|
||||||
ehps = self.tank
|
ehps = self.tank
|
||||||
else:
|
else:
|
||||||
ehps = self.damagePattern.calculateEffectiveTank(self, self.extraAttributes)
|
ehps = self.damagePattern.calculateEffectiveTank(self, self.tank)
|
||||||
|
|
||||||
self.__effectiveTank = ehps
|
self.__effectiveTank = ehps
|
||||||
|
|
||||||
return self.__effectiveTank
|
return self.__effectiveTank
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sustainableTank(self):
|
||||||
|
if self.__sustainableTank is None:
|
||||||
|
self.calculateSustainableTank()
|
||||||
|
|
||||||
|
return self.__sustainableTank
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def effectiveSustainableTank(self):
|
def effectiveSustainableTank(self):
|
||||||
if self.__effectiveSustainableTank is None:
|
if self.__effectiveSustainableTank is None:
|
||||||
if self.damagePattern is None:
|
if self.damagePattern is None:
|
||||||
eshps = self.sustainableTank
|
tank = self.sustainableTank
|
||||||
else:
|
else:
|
||||||
eshps = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
tank = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
||||||
|
self.__effectiveSustainableTank = tank
|
||||||
self.__effectiveSustainableTank = eshps
|
|
||||||
|
|
||||||
return self.__effectiveSustainableTank
|
return self.__effectiveSustainableTank
|
||||||
|
|
||||||
|
def calculateSustainableTank(self):
|
||||||
|
if self.__sustainableTank is None:
|
||||||
|
sustainable = {
|
||||||
|
"passiveShield": self.calculateShieldRecharge(),
|
||||||
|
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||||
|
"armorRepair": self.extraAttributes["armorRepair"],
|
||||||
|
"armorRepairPreSpool": self.extraAttributes["armorRepairPreSpool"],
|
||||||
|
"armorRepairFullSpool": self.extraAttributes["armorRepairFullSpool"],
|
||||||
|
"hullRepair": self.extraAttributes["hullRepair"]}
|
||||||
|
if not self.capStable or self.factorReload:
|
||||||
|
# Map a local repairer type to the attribute it uses
|
||||||
|
groupAttrMap = {
|
||||||
|
"Shield Booster": "shieldBonus",
|
||||||
|
"Ancillary Shield Booster": "shieldBonus",
|
||||||
|
"Armor Repair Unit": "armorDamageAmount",
|
||||||
|
"Ancillary Armor Repairer": "armorDamageAmount",
|
||||||
|
"Hull Repair Unit": "structureDamageAmount"}
|
||||||
|
# Map local repairer type to tank type
|
||||||
|
groupStoreMap = {
|
||||||
|
"Shield Booster": "shieldRepair",
|
||||||
|
"Ancillary Shield Booster": "shieldRepair",
|
||||||
|
"Armor Repair Unit": "armorRepair",
|
||||||
|
"Ancillary Armor Repairer": "armorRepair",
|
||||||
|
"Hull Repair Unit": "hullRepair"}
|
||||||
|
repairers = []
|
||||||
|
localAdjustment = {"shieldRepair": 0, "armorRepair": 0, "hullRepair": 0}
|
||||||
|
capUsed = self.capUsed
|
||||||
|
for tankType in localAdjustment:
|
||||||
|
dict = self.extraAttributes.getAfflictions(tankType)
|
||||||
|
if self in dict:
|
||||||
|
for mod, _, amount, used in dict[self]:
|
||||||
|
if not used:
|
||||||
|
continue
|
||||||
|
if mod.projected:
|
||||||
|
continue
|
||||||
|
if mod.item.group.name not in groupAttrMap:
|
||||||
|
continue
|
||||||
|
usesCap = True
|
||||||
|
try:
|
||||||
|
if mod.capUse:
|
||||||
|
capUsed -= mod.capUse
|
||||||
|
else:
|
||||||
|
usesCap = False
|
||||||
|
except AttributeError:
|
||||||
|
usesCap = False
|
||||||
|
|
||||||
|
# Normal Repairers
|
||||||
|
if usesCap and not mod.charge:
|
||||||
|
cycleTime = mod.rawCycleTime
|
||||||
|
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
||||||
|
localAdjustment[tankType] -= amount / (cycleTime / 1000.0)
|
||||||
|
repairers.append(mod)
|
||||||
|
# Ancillary Armor reps etc
|
||||||
|
elif usesCap and mod.charge:
|
||||||
|
cycleTime = mod.rawCycleTime
|
||||||
|
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
||||||
|
if mod.charge.name == "Nanite Repair Paste":
|
||||||
|
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
||||||
|
else:
|
||||||
|
multiplier = 1
|
||||||
|
localAdjustment[tankType] -= amount * multiplier / (cycleTime / 1000.0)
|
||||||
|
repairers.append(mod)
|
||||||
|
# Ancillary Shield boosters etc
|
||||||
|
elif not usesCap and mod.item.group.name in ("Ancillary Shield Booster", "Ancillary Remote Shield Booster"):
|
||||||
|
cycleTime = mod.rawCycleTime
|
||||||
|
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
||||||
|
if self.factorReload and mod.charge:
|
||||||
|
reloadtime = mod.reloadTime
|
||||||
|
else:
|
||||||
|
reloadtime = 0.0
|
||||||
|
offdutycycle = reloadtime / ((max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||||
|
localAdjustment[tankType] -= amount * offdutycycle / (cycleTime / 1000.0)
|
||||||
|
|
||||||
|
# Sort repairers by efficiency. We want to use the most efficient repairers first
|
||||||
|
repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr(
|
||||||
|
groupAttrMap[_mod.item.group.name]) * (_mod.getModifiedItemAttr(
|
||||||
|
"chargedArmorDamageMultiplier") or 1) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True)
|
||||||
|
|
||||||
|
# Loop through every module until we're above peak recharge
|
||||||
|
# Most efficient first, as we sorted earlier.
|
||||||
|
# calculate how much the repper can rep stability & add to total
|
||||||
|
totalPeakRecharge = self.capRecharge
|
||||||
|
for mod in repairers:
|
||||||
|
if capUsed > totalPeakRecharge:
|
||||||
|
break
|
||||||
|
|
||||||
|
if self.factorReload and mod.charge:
|
||||||
|
reloadtime = mod.reloadTime
|
||||||
|
else:
|
||||||
|
reloadtime = 0.0
|
||||||
|
|
||||||
|
cycleTime = mod.rawCycleTime
|
||||||
|
capPerSec = mod.capUse
|
||||||
|
|
||||||
|
if capPerSec is not None and cycleTime is not None:
|
||||||
|
# Check how much this repper can work
|
||||||
|
sustainability = min(1, (totalPeakRecharge - capUsed) / capPerSec)
|
||||||
|
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
|
||||||
|
# Add the sustainable amount
|
||||||
|
if not mod.charge:
|
||||||
|
localAdjustment[groupStoreMap[mod.item.group.name]] += sustainability * amount / (
|
||||||
|
cycleTime / 1000.0)
|
||||||
|
else:
|
||||||
|
if mod.charge.name == "Nanite Repair Paste":
|
||||||
|
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
||||||
|
else:
|
||||||
|
multiplier = 1
|
||||||
|
ondutycycle = (max(mod.numShots, 1) * cycleTime) / (
|
||||||
|
(max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||||
|
localAdjustment[groupStoreMap[
|
||||||
|
mod.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
|
||||||
|
cycleTime / 1000.0)
|
||||||
|
|
||||||
|
capUsed += capPerSec
|
||||||
|
sustainable["shieldRepair"] += localAdjustment["shieldRepair"]
|
||||||
|
sustainable["armorRepair"] += localAdjustment["armorRepair"]
|
||||||
|
sustainable["armorRepairPreSpool"] += localAdjustment["armorRepair"]
|
||||||
|
sustainable["armorRepairFullSpool"] += localAdjustment["armorRepair"]
|
||||||
|
sustainable["hullRepair"] += localAdjustment["hullRepair"]
|
||||||
|
|
||||||
|
self.__sustainableTank = sustainable
|
||||||
|
|
||||||
|
return self.__sustainableTank
|
||||||
|
|
||||||
def calculateLockTime(self, radius):
|
def calculateLockTime(self, radius):
|
||||||
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
||||||
if scanRes is not None and scanRes > 0:
|
if scanRes is not None and scanRes > 0:
|
||||||
@@ -1537,30 +1445,30 @@ class Fit(object):
|
|||||||
self.__minerYield = minerYield
|
self.__minerYield = minerYield
|
||||||
self.__droneYield = droneYield
|
self.__droneYield = droneYield
|
||||||
|
|
||||||
def calculateWeaponStats(self):
|
def calculateWeaponDmgStats(self, spoolOptions):
|
||||||
weaponDPS = 0
|
weaponVolley = DmgTypes(0, 0, 0, 0)
|
||||||
droneDPS = 0
|
weaponDps = DmgTypes(0, 0, 0, 0)
|
||||||
weaponVolley = 0
|
|
||||||
droneVolley = 0
|
|
||||||
|
|
||||||
for mod in self.modules:
|
for mod in self.modules:
|
||||||
dps, volley = mod.damageStats(self.targetResists)
|
weaponVolley += mod.getVolley(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||||
weaponDPS += dps
|
weaponDps += mod.getDps(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||||
weaponVolley += volley
|
|
||||||
|
self.__weaponVolleyMap[spoolOptions] = weaponVolley
|
||||||
|
self.__weaponDpsMap[spoolOptions] = weaponDps
|
||||||
|
|
||||||
|
def calculateDroneDmgStats(self):
|
||||||
|
droneVolley = DmgTypes(0, 0, 0, 0)
|
||||||
|
droneDps = DmgTypes(0, 0, 0, 0)
|
||||||
|
|
||||||
for drone in self.drones:
|
for drone in self.drones:
|
||||||
dps, volley = drone.damageStats(self.targetResists)
|
droneVolley += drone.getVolley(targetResists=self.targetResists)
|
||||||
droneDPS += dps
|
droneDps += drone.getDps(targetResists=self.targetResists)
|
||||||
droneVolley += volley
|
|
||||||
|
|
||||||
for fighter in self.fighters:
|
for fighter in self.fighters:
|
||||||
dps, volley = fighter.damageStats(self.targetResists)
|
droneVolley += fighter.getVolley(targetResists=self.targetResists)
|
||||||
droneDPS += dps
|
droneDps += fighter.getDps(targetResists=self.targetResists)
|
||||||
droneVolley += volley
|
|
||||||
|
|
||||||
self.__weaponDPS = weaponDPS
|
self.__droneDps = droneDps
|
||||||
self.__weaponVolley = weaponVolley
|
|
||||||
self.__droneDPS = droneDPS
|
|
||||||
self.__droneVolley = droneVolley
|
self.__droneVolley = droneVolley
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ from eos.enum import Enum
|
|||||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||||
from eos.saveddata.citadel import Citadel
|
from eos.saveddata.citadel import Citadel
|
||||||
from eos.saveddata.mutator import Mutator
|
from eos.saveddata.mutator import Mutator
|
||||||
|
from eos.utils.float import floatUnerr
|
||||||
|
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||||
|
from eos.utils.stats import DmgTypes
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -95,7 +98,6 @@ class Hardpoint(Enum):
|
|||||||
|
|
||||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
|
||||||
MINING_ATTRIBUTES = ("miningAmount",)
|
MINING_ATTRIBUTES = ("miningAmount",)
|
||||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
||||||
|
|
||||||
@@ -168,9 +170,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if self.__charge and self.__charge.category.name != "Charge":
|
if self.__charge and self.__charge.category.name != "Charge":
|
||||||
self.__charge = None
|
self.__charge = None
|
||||||
|
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
|
self.__baseRemoteReps = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__volley = None
|
|
||||||
self.__reloadTime = None
|
self.__reloadTime = None
|
||||||
self.__reloadForce = None
|
self.__reloadForce = None
|
||||||
self.__chargeCycles = None
|
self.__chargeCycles = None
|
||||||
@@ -247,8 +249,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
if chargeVolume is None or containerCapacity is None:
|
if chargeVolume is None or containerCapacity is None:
|
||||||
charges = 0
|
charges = 0
|
||||||
else:
|
else:
|
||||||
charges = floor(containerCapacity / chargeVolume)
|
charges = int(floatUnerr(containerCapacity / chargeVolume))
|
||||||
return int(charges)
|
return charges
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def numShots(self):
|
def numShots(self):
|
||||||
@@ -411,35 +413,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
self.__itemModifiedAttributes.clear()
|
self.__itemModifiedAttributes.clear()
|
||||||
|
|
||||||
def damageStats(self, targetResists):
|
|
||||||
if self.__dps is None:
|
|
||||||
self.__dps = 0
|
|
||||||
self.__volley = 0
|
|
||||||
|
|
||||||
if not self.isEmpty and self.state >= State.ACTIVE:
|
|
||||||
if self.charge:
|
|
||||||
func = self.getModifiedChargeAttr
|
|
||||||
else:
|
|
||||||
func = self.getModifiedItemAttr
|
|
||||||
|
|
||||||
volley = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
|
||||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
|
||||||
# Disintegrator-specific ramp-up multiplier
|
|
||||||
volley *= (self.getModifiedItemAttr("damageMultiplierBonusMax") or 0) + 1
|
|
||||||
if volley:
|
|
||||||
cycleTime = self.cycleTime
|
|
||||||
# Some weapons repeat multiple times in one cycle (think doomsdays)
|
|
||||||
# Get the number of times it fires off
|
|
||||||
weaponDoT = max(
|
|
||||||
self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
self.__volley = volley
|
|
||||||
self.__dps = (volley * weaponDoT) / (cycleTime / 1000.0)
|
|
||||||
|
|
||||||
return self.__dps, self.__volley
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def miningStats(self):
|
def miningStats(self):
|
||||||
if self.__miningyield is None:
|
if self.__miningyield is None:
|
||||||
@@ -459,13 +432,110 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
|
|
||||||
return self.__miningyield
|
return self.__miningyield
|
||||||
|
|
||||||
@property
|
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||||
def dps(self):
|
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||||
return self.damageStats(None)[0]
|
return DmgTypes(0, 0, 0, 0)
|
||||||
|
if self.__baseVolley is None:
|
||||||
|
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||||
|
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||||
|
self.__baseVolley = DmgTypes(
|
||||||
|
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||||
|
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||||
|
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||||
|
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||||
|
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||||
|
spoolBoost = calculateSpoolup(
|
||||||
|
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
|
||||||
|
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
|
||||||
|
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||||
|
spoolMultiplier = 1 + spoolBoost
|
||||||
|
volley = DmgTypes(
|
||||||
|
em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
|
||||||
|
thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||||
|
kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||||
|
explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||||
|
return volley
|
||||||
|
|
||||||
@property
|
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||||
def volley(self):
|
volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||||
return self.damageStats(None)[1]
|
if not volley:
|
||||||
|
return DmgTypes(0, 0, 0, 0)
|
||||||
|
# Some weapons repeat multiple times in one cycle (bosonic doomsdays). Get the number of times it fires off
|
||||||
|
volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1)
|
||||||
|
dpsFactor = volleysPerCycle / (self.cycleTime / 1000)
|
||||||
|
dps = DmgTypes(
|
||||||
|
em=volley.em * dpsFactor,
|
||||||
|
thermal=volley.thermal * dpsFactor,
|
||||||
|
kinetic=volley.kinetic * dpsFactor,
|
||||||
|
explosive=volley.explosive * dpsFactor)
|
||||||
|
return dps
|
||||||
|
|
||||||
|
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
|
||||||
|
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
def getBaseRemoteReps(module):
|
||||||
|
remoteModuleGroups = {
|
||||||
|
"Remote Armor Repairer": "Armor",
|
||||||
|
"Ancillary Remote Armor Repairer": "Armor",
|
||||||
|
"Mutadaptive Remote Armor Repairer": "Armor",
|
||||||
|
"Remote Hull Repairer": "Hull",
|
||||||
|
"Remote Shield Booster": "Shield",
|
||||||
|
"Ancillary Remote Shield Booster": "Shield",
|
||||||
|
"Remote Capacitor Transmitter": "Capacitor"}
|
||||||
|
rrType = remoteModuleGroups.get(module.item.group.name, None)
|
||||||
|
if not rrType:
|
||||||
|
return None, 0
|
||||||
|
if rrType == "Hull":
|
||||||
|
rrAmount = module.getModifiedItemAttr("structureDamageAmount", 0)
|
||||||
|
elif rrType == "Armor":
|
||||||
|
rrAmount = module.getModifiedItemAttr("armorDamageAmount", 0)
|
||||||
|
elif rrType == "Shield":
|
||||||
|
rrAmount = module.getModifiedItemAttr("shieldBonus", 0)
|
||||||
|
elif rrType == "Capacitor":
|
||||||
|
rrAmount = module.getModifiedItemAttr("powerTransferAmount", 0)
|
||||||
|
else:
|
||||||
|
return None, 0
|
||||||
|
if rrAmount:
|
||||||
|
rrAmount *= 1 / (self.cycleTime / 1000)
|
||||||
|
if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
|
||||||
|
rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
||||||
|
|
||||||
|
return rrType, rrAmount
|
||||||
|
|
||||||
|
if self.__baseRemoteReps is None:
|
||||||
|
self.__baseRemoteReps = getBaseRemoteReps(self)
|
||||||
|
|
||||||
|
rrType, rrAmount = self.__baseRemoteReps
|
||||||
|
|
||||||
|
if rrType and rrAmount and self.item.group.name == "Mutadaptive Remote Armor Repairer":
|
||||||
|
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||||
|
spoolBoost = calculateSpoolup(
|
||||||
|
self.getModifiedItemAttr("repairMultiplierBonusMax", 0),
|
||||||
|
self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0),
|
||||||
|
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||||
|
rrAmount *= (1 + spoolBoost)
|
||||||
|
|
||||||
|
return rrType, rrAmount
|
||||||
|
|
||||||
|
def getSpoolData(self, spoolOptions=None):
|
||||||
|
weaponMultMax = self.getModifiedItemAttr("damageMultiplierBonusMax", 0)
|
||||||
|
weaponMultPerCycle = self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0)
|
||||||
|
if weaponMultMax and weaponMultPerCycle:
|
||||||
|
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||||
|
_, spoolCycles, spoolTime = calculateSpoolup(
|
||||||
|
weaponMultMax, weaponMultPerCycle,
|
||||||
|
self.rawCycleTime / 1000, spoolType, spoolAmount)
|
||||||
|
return spoolCycles, spoolTime
|
||||||
|
rrMultMax = self.getModifiedItemAttr("repairMultiplierBonusMax", 0)
|
||||||
|
rrMultPerCycle = self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0)
|
||||||
|
if rrMultMax and rrMultPerCycle:
|
||||||
|
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||||
|
_, spoolCycles, spoolTime = calculateSpoolup(
|
||||||
|
rrMultMax, rrMultPerCycle,
|
||||||
|
self.rawCycleTime / 1000, spoolType, spoolAmount)
|
||||||
|
return spoolCycles, spoolTime
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reloadTime(self):
|
def reloadTime(self):
|
||||||
@@ -718,9 +788,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
self.__dps = None
|
self.__baseVolley = None
|
||||||
|
self.__baseRemoteReps = None
|
||||||
self.__miningyield = None
|
self.__miningyield = None
|
||||||
self.__volley = None
|
|
||||||
self.__reloadTime = None
|
self.__reloadTime = None
|
||||||
self.__reloadForce = None
|
self.__reloadForce = None
|
||||||
self.__chargeCycles = None
|
self.__chargeCycles = None
|
||||||
|
|||||||
@@ -18,24 +18,30 @@
|
|||||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from sqlalchemy.orm import reconstructor
|
import time
|
||||||
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
from logbook import Logger
|
from logbook import Logger
|
||||||
|
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class PriceStatus(IntEnum):
|
||||||
|
notFetched = 0
|
||||||
|
success = 1
|
||||||
|
fail = 2
|
||||||
|
notSupported = 3
|
||||||
|
|
||||||
|
|
||||||
class Price(object):
|
class Price(object):
|
||||||
def __init__(self, typeID):
|
def __init__(self, typeID):
|
||||||
self.typeID = typeID
|
self.typeID = typeID
|
||||||
self.time = 0
|
self.time = 0
|
||||||
self.__price = 0
|
self.__price = 0
|
||||||
self.failed = None
|
self.status = PriceStatus.notFetched
|
||||||
|
|
||||||
@reconstructor
|
|
||||||
def init(self):
|
|
||||||
self.__item = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isValid(self):
|
def isValid(self):
|
||||||
@@ -43,7 +49,10 @@ class Price(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def price(self):
|
def price(self):
|
||||||
return self.__price or 0.0
|
if self.status != PriceStatus.success:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self.__price or 0
|
||||||
|
|
||||||
@price.setter
|
@price.setter
|
||||||
def price(self, price):
|
def price(self, price):
|
||||||
|
|||||||
@@ -29,14 +29,16 @@ pyfalog = Logger(__name__)
|
|||||||
|
|
||||||
class Ship(ItemAttrShortcut, HandledItem):
|
class Ship(ItemAttrShortcut, HandledItem):
|
||||||
EXTRA_ATTRIBUTES = {
|
EXTRA_ATTRIBUTES = {
|
||||||
"armorRepair" : 0,
|
"armorRepair": 0,
|
||||||
"hullRepair" : 0,
|
"armorRepairPreSpool": 0,
|
||||||
"shieldRepair" : 0,
|
"armorRepairFullSpool": 0,
|
||||||
"maxActiveDrones" : 0,
|
"hullRepair": 0,
|
||||||
|
"shieldRepair": 0,
|
||||||
|
"maxActiveDrones": 0,
|
||||||
"maxTargetsLockedFromSkills": 2,
|
"maxTargetsLockedFromSkills": 2,
|
||||||
"droneControlRange" : 20000,
|
"droneControlRange": 20000,
|
||||||
"cloaked" : False,
|
"cloaked": False,
|
||||||
"siege" : False
|
"siege": False
|
||||||
# We also have speedLimit for Entosis Link, but there seems to be an
|
# We also have speedLimit for Entosis Link, but there seems to be an
|
||||||
# issue with naming it exactly "speedLimit" due to unknown reasons.
|
# issue with naming it exactly "speedLimit" due to unknown reasons.
|
||||||
# Regardless, we don't have to put it here anyways - it will come up
|
# Regardless, we don't have to put it here anyways - it will come up
|
||||||
|
|||||||
0
eos/utils/__init__.py
Normal file
0
eos/utils/__init__.py
Normal file
26
eos/utils/float.py
Normal file
26
eos/utils/float.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Sometimes use of floats may lead to undesirable results, e.g.
|
||||||
|
int(2.3 / 0.1) = 22.
|
||||||
|
We cannot afford to use different number representations (e.g. representations
|
||||||
|
provided by decimal or fraction modules), thus consequences are worked around by
|
||||||
|
this module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
# As we will be rounding numbers after operations (which introduce higher error
|
||||||
|
# than base float representation error), we need to keep less significant
|
||||||
|
# numbers than for single float number w/o operations
|
||||||
|
keepDigits = int(sys.float_info.dig / 2)
|
||||||
|
|
||||||
|
|
||||||
|
def floatUnerr(value):
|
||||||
|
"""Round possible float number error, killing some precision in process."""
|
||||||
|
if value == 0:
|
||||||
|
return value
|
||||||
|
# Find round factor, taking into consideration that we want to keep at least
|
||||||
|
# predefined amount of significant digits
|
||||||
|
roundFactor = int(keepDigits - math.ceil(math.log10(abs(value))))
|
||||||
|
return round(value, roundFactor)
|
||||||
70
eos/utils/spoolSupport.py
Normal file
70
eos/utils/spoolSupport.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# ===============================================================================
|
||||||
|
# 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 collections import namedtuple
|
||||||
|
from enum import IntEnum, unique
|
||||||
|
|
||||||
|
from eos.utils.float import floatUnerr
|
||||||
|
|
||||||
|
|
||||||
|
SpoolOptions = namedtuple('SpoolOptions', ('spoolType', 'spoolAmount', 'force'))
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class SpoolType(IntEnum):
|
||||||
|
SCALE = 0 # [0..1]
|
||||||
|
TIME = 1 # Expressed via time in seconds since spool up started
|
||||||
|
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||||
|
|
||||||
|
|
||||||
|
def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAmount):
|
||||||
|
"""
|
||||||
|
Calculate damage multiplier increment based on passed parameters. Module cycle time
|
||||||
|
is specified in seconds.
|
||||||
|
|
||||||
|
Returns spoolup value, amount of cycles to reach it and time to reach it.
|
||||||
|
"""
|
||||||
|
if not modMaxValue or not modStepValue:
|
||||||
|
return 0, 0, 0
|
||||||
|
if spoolType == SpoolType.SCALE:
|
||||||
|
cycles = int(floatUnerr(spoolAmount * modMaxValue / modStepValue))
|
||||||
|
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||||
|
elif spoolType == SpoolType.TIME:
|
||||||
|
cycles = min(int(floatUnerr(spoolAmount / modCycleTime)), int(floatUnerr(modMaxValue / modStepValue)))
|
||||||
|
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||||
|
elif spoolType == SpoolType.CYCLES:
|
||||||
|
cycles = min(int(spoolAmount), int(floatUnerr(modMaxValue / modStepValue)))
|
||||||
|
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||||
|
else:
|
||||||
|
return 0, 0, 0
|
||||||
|
|
||||||
|
|
||||||
|
def resolveSpoolOptions(spoolOptions, module):
|
||||||
|
# Rely on passed options if they are forcing us to do so
|
||||||
|
if spoolOptions is not None and spoolOptions.force:
|
||||||
|
return spoolOptions.spoolType, spoolOptions.spoolAmount
|
||||||
|
# If we're not forced to use options and module has options set, prefer on-module values
|
||||||
|
elif module is not None and module.spoolType is not None:
|
||||||
|
return module.spoolType, module.spoolAmount
|
||||||
|
# Otherwise - rely on passed options
|
||||||
|
elif spoolOptions is not None:
|
||||||
|
return spoolOptions.spoolType, spoolOptions.spoolAmount
|
||||||
|
else:
|
||||||
|
return None, None
|
||||||
70
eos/utils/stats.py
Normal file
70
eos/utils/stats.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# ===============================================================================
|
||||||
|
# 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/>.
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class DmgTypes:
|
||||||
|
"""Container for damage data stats."""
|
||||||
|
|
||||||
|
def __init__(self, em, thermal, kinetic, explosive):
|
||||||
|
self.em = em
|
||||||
|
self.thermal = thermal
|
||||||
|
self.kinetic = kinetic
|
||||||
|
self.explosive = explosive
|
||||||
|
self._calcTotal()
|
||||||
|
|
||||||
|
# Iterator is needed to support tuple-style unpacking
|
||||||
|
def __iter__(self):
|
||||||
|
yield self.em
|
||||||
|
yield self.thermal
|
||||||
|
yield self.kinetic
|
||||||
|
yield self.explosive
|
||||||
|
yield self.total
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, DmgTypes):
|
||||||
|
return NotImplemented
|
||||||
|
return all((
|
||||||
|
self.em == other.em,
|
||||||
|
self.thermal == other.thermal,
|
||||||
|
self.kinetic == other.kinetic,
|
||||||
|
self.explosive == other.explosive,
|
||||||
|
self.total == other.total))
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return any((
|
||||||
|
self.em, self.thermal, self.kinetic,
|
||||||
|
self.explosive, self.total))
|
||||||
|
|
||||||
|
def _calcTotal(self):
|
||||||
|
self.total = self.em + self.thermal + self.kinetic + self.explosive
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return type(self)(
|
||||||
|
em=self.em + other.em,
|
||||||
|
thermal=self.thermal + other.thermal,
|
||||||
|
kinetic=self.kinetic + other.kinetic,
|
||||||
|
explosive=self.explosive + other.explosive)
|
||||||
|
|
||||||
|
def __iadd__(self, other):
|
||||||
|
self.em += other.em
|
||||||
|
self.thermal += other.thermal
|
||||||
|
self.kinetic += other.kinetic
|
||||||
|
self.explosive += other.explosive
|
||||||
|
self._calcTotal()
|
||||||
|
return self
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)
|
versionString = "{0}".format(config.version)
|
||||||
licenses = (
|
licenses = (
|
||||||
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
||||||
"All EVE-Online related materials are property of CCP hf.",
|
"All EVE-Online related materials are property of CCP hf.",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import wx
|
|||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
import gui.globalEvents as GE
|
import gui.globalEvents as GE
|
||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
|
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||||
|
|
||||||
|
|
||||||
class NotesView(wx.Panel):
|
class NotesView(wx.Panel):
|
||||||
@@ -17,9 +18,16 @@ class NotesView(wx.Panel):
|
|||||||
self.SetSizer(mainSizer)
|
self.SetSizer(mainSizer)
|
||||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||||
self.Bind(wx.EVT_TEXT, self.onText)
|
self.Bind(wx.EVT_TEXT, self.onText)
|
||||||
|
self.editNotes.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
||||||
self.saveTimer = wx.Timer(self)
|
self.saveTimer = wx.Timer(self)
|
||||||
self.Bind(wx.EVT_TIMER, self.delayedSave, self.saveTimer)
|
self.Bind(wx.EVT_TIMER, self.delayedSave, self.saveTimer)
|
||||||
|
|
||||||
|
def OnKeyDown(self, event):
|
||||||
|
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||||
|
HandleCtrlBackspace(self.editNotes)
|
||||||
|
else:
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def fitChanged(self, event):
|
def fitChanged(self, event):
|
||||||
sFit = Fit.getInstance()
|
sFit = Fit.getInstance()
|
||||||
fit = sFit.getFit(event.fitID)
|
fit = sFit.getFit(event.fitID)
|
||||||
|
|||||||
34
gui/builtinContextMenus/fillWithModule.py
Normal file
34
gui/builtinContextMenus/fillWithModule.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from gui.contextMenu import ContextMenu
|
||||||
|
import gui.mainFrame
|
||||||
|
# noinspection PyPackageRequirements
|
||||||
|
import wx
|
||||||
|
import gui.globalEvents as GE
|
||||||
|
from service.settings import ContextMenuSettings
|
||||||
|
import gui.fitCommands as cmd
|
||||||
|
|
||||||
|
|
||||||
|
class FillWithModule(ContextMenu):
|
||||||
|
def __init__(self):
|
||||||
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||||
|
self.settings = ContextMenuSettings.getInstance()
|
||||||
|
|
||||||
|
def display(self, srcContext, selection):
|
||||||
|
if not self.settings.get('moduleFill'):
|
||||||
|
return False
|
||||||
|
return srcContext in ("fittingModule")
|
||||||
|
|
||||||
|
def getText(self, itmContext, selection):
|
||||||
|
return u"Fill With {0}".format(itmContext if itmContext is not None else "Module")
|
||||||
|
|
||||||
|
def activate(self, fullContext, selection, i):
|
||||||
|
|
||||||
|
srcContext = fullContext[0]
|
||||||
|
fitID = self.mainFrame.getActiveFit()
|
||||||
|
|
||||||
|
if srcContext == "fittingModule":
|
||||||
|
self.mainFrame.command.Submit(cmd.GuiFillWithModuleCommand(fitID, selection[0].itemID))
|
||||||
|
return # the command takes care of the PostEvent
|
||||||
|
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||||
|
|
||||||
|
|
||||||
|
FillWithModule.register()
|
||||||
@@ -26,7 +26,10 @@ class MarketJump(ContextMenu):
|
|||||||
|
|
||||||
sMkt = Market.getInstance()
|
sMkt = Market.getInstance()
|
||||||
item = getattr(selection[0], "item", selection[0])
|
item = getattr(selection[0], "item", selection[0])
|
||||||
|
isMutated = getattr(selection[0], "isMutated", False)
|
||||||
mktGrp = sMkt.getMarketGroupByItem(item)
|
mktGrp = sMkt.getMarketGroupByItem(item)
|
||||||
|
if mktGrp is None and isMutated:
|
||||||
|
mktGrp = sMkt.getMarketGroupByItem(selection[0].baseItem)
|
||||||
|
|
||||||
# 1663 is Special Edition Festival Assets, we don't have root group for it
|
# 1663 is Special Edition Festival Assets, we don't have root group for it
|
||||||
if mktGrp is None or mktGrp.ID == 1663:
|
if mktGrp is None or mktGrp.ID == 1663:
|
||||||
@@ -43,7 +46,10 @@ class MarketJump(ContextMenu):
|
|||||||
if srcContext in ("fittingCharge", "projectedCharge"):
|
if srcContext in ("fittingCharge", "projectedCharge"):
|
||||||
item = selection[0].charge
|
item = selection[0].charge
|
||||||
elif hasattr(selection[0], "item"):
|
elif hasattr(selection[0], "item"):
|
||||||
item = selection[0].item
|
if getattr(selection[0], "isMutated", False):
|
||||||
|
item = selection[0].baseItem
|
||||||
|
else:
|
||||||
|
item = selection[0].item
|
||||||
else:
|
else:
|
||||||
item = selection[0]
|
item = selection[0]
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
import wx
|
import wx
|
||||||
import wx.lib.newevent
|
import wx.lib.newevent
|
||||||
|
|
||||||
from gui.attribute_gauge import AttributeGauge
|
from gui.attribute_gauge import AttributeGauge
|
||||||
|
from eos.utils.float import floatUnerr
|
||||||
|
|
||||||
_ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
|
_ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
|
||||||
|
|
||||||
@@ -58,22 +61,41 @@ class AttributeSlider(wx.Panel):
|
|||||||
|
|
||||||
self.inverse = inverse
|
self.inverse = inverse
|
||||||
|
|
||||||
# The internal slider basically represents the percentage towards the end of the range. It has to be normalized
|
def getStep(valRange):
|
||||||
# in this way, otherwise when we start off with a base, if the range is skewed to one side, the base value won't
|
"""
|
||||||
# be centered. We use a range of -100,100 so that we can depend on the SliderValue to contain the percentage
|
Find step for the passed range, which is based on 1, 2 or 5.
|
||||||
# toward one end
|
Step returned will make sure that range fits 10..50 of them,
|
||||||
|
as close to 10 as possible.
|
||||||
|
"""
|
||||||
|
steps = {1: None, 2: None, 5: None}
|
||||||
|
for baseInc in steps:
|
||||||
|
baseIncAmount = valRange / baseInc
|
||||||
|
incScale = math.floor(math.log10(baseIncAmount) - 1)
|
||||||
|
steps[baseInc] = baseInc * 10 ** incScale
|
||||||
|
chosenBase = min(steps, key=lambda base: valRange / steps[base])
|
||||||
|
chosenStep = steps[chosenBase]
|
||||||
|
if inverse:
|
||||||
|
chosenStep *= -1
|
||||||
|
return chosenStep
|
||||||
|
|
||||||
# Additionally, since we want the slider to be accurate to 3 decimal places, we need to blow out the two ends here
|
def getDigitPlaces(minValue, maxValue):
|
||||||
# (if we have a slider that needs to land on 66.66% towards the right, it will actually be converted to 66%. Se we need it to support 6,666)
|
minDigits = 3
|
||||||
#
|
maxDigits = 5
|
||||||
# self.SliderMinValue = -100
|
currentDecision = minDigits
|
||||||
# self.SliderMaxValue = 100
|
for value in (floatUnerr(minValue), floatUnerr(maxValue)):
|
||||||
# self.SliderValue = 0
|
for currentDigit in range(minDigits, maxDigits + 1):
|
||||||
|
if round(value, currentDigit) == value:
|
||||||
|
if currentDigit > currentDecision:
|
||||||
|
currentDecision = currentDigit
|
||||||
|
break
|
||||||
|
# Max decimal places we can afford to show was not enough
|
||||||
|
else:
|
||||||
|
return maxDigits
|
||||||
|
return currentDecision
|
||||||
|
|
||||||
range = [self.UserMinValue, self.UserMaxValue]
|
self.ctrl = wx.SpinCtrlDouble(self, min=minValue, max=maxValue, inc=getStep(maxValue - minValue))
|
||||||
|
self.ctrl.SetDigits(getDigitPlaces(minValue, maxValue))
|
||||||
|
|
||||||
self.ctrl = wx.SpinCtrlDouble(self, min=min(range), max=max(range))
|
|
||||||
self.ctrl.SetDigits(3)
|
|
||||||
|
|
||||||
self.ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.UpdateValue)
|
self.ctrl.Bind(wx.EVT_SPINCTRLDOUBLE, self.UpdateValue)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ from service.attribute import Attribute
|
|||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount
|
||||||
|
|
||||||
|
|
||||||
|
def defaultSort(item):
|
||||||
|
return (item.attributes['metaLevel'].value if 'metaLevel' in item.attributes else 0, item.name)
|
||||||
|
|
||||||
|
|
||||||
class ItemCompare(wx.Panel):
|
class ItemCompare(wx.Panel):
|
||||||
def __init__(self, parent, stuff, item, items, context=None):
|
def __init__(self, parent, stuff, item, items, context=None):
|
||||||
# Start dealing with Price stuff to get that thread going
|
# Start dealing with Price stuff to get that thread going
|
||||||
@@ -27,8 +31,7 @@ class ItemCompare(wx.Panel):
|
|||||||
self.currentSort = None
|
self.currentSort = None
|
||||||
self.sortReverse = False
|
self.sortReverse = False
|
||||||
self.item = item
|
self.item = item
|
||||||
self.items = sorted(items,
|
self.items = sorted(items, key=defaultSort)
|
||||||
key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0)
|
|
||||||
self.attrs = {}
|
self.attrs = {}
|
||||||
|
|
||||||
# get a dict of attrName: attrInfo of all unique attributes across all items
|
# get a dict of attrName: attrInfo of all unique attributes across all items
|
||||||
@@ -126,10 +129,15 @@ class ItemCompare(wx.Panel):
|
|||||||
# starts at 0 while the list has the item name as column 0.
|
# starts at 0 while the list has the item name as column 0.
|
||||||
attr = str(list(self.attrs.keys())[sort - 1])
|
attr = str(list(self.attrs.keys())[sort - 1])
|
||||||
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
|
||||||
|
# Clicked on a column that's not part of our array (price most likely)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# Clicked on a column that's not part of our array (price most likely)
|
# Price
|
||||||
self.sortReverse = False
|
if sort == len(self.attrs) + 1:
|
||||||
func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0
|
func = lambda i: i.price.price if i.price.price != 0 else float("Inf")
|
||||||
|
# Something else
|
||||||
|
else:
|
||||||
|
self.sortReverse = False
|
||||||
|
func = defaultSort
|
||||||
|
|
||||||
self.items = sorted(self.items, key=func, reverse=self.sortReverse)
|
self.items = sorted(self.items, key=func, reverse=self.sortReverse)
|
||||||
|
|
||||||
@@ -160,7 +168,7 @@ class ItemCompare(wx.Panel):
|
|||||||
self.paramList.SetItem(i, x + 1, valueUnit)
|
self.paramList.SetItem(i, x + 1, valueUnit)
|
||||||
|
|
||||||
# Add prices
|
# Add prices
|
||||||
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True))
|
self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True) if item.price.price else "")
|
||||||
|
|
||||||
self.paramList.RefreshRows()
|
self.paramList.RefreshRows()
|
||||||
self.Layout()
|
self.Layout()
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class ItemDescription(wx.Panel):
|
|||||||
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
||||||
|
|
||||||
self.description = wx.html.HtmlWindow(self)
|
self.description = wx.html.HtmlWindow(self)
|
||||||
|
|
||||||
if not item.description:
|
if not item.description:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -24,8 +23,11 @@ class ItemDescription(wx.Panel):
|
|||||||
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
|
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
|
||||||
# Strip URLs
|
# Strip URLs
|
||||||
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
|
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
|
||||||
desc = "<body bgcolor='" + bgcolor.GetAsString(wx.C2S_HTML_SYNTAX) + "' text='" + fgcolor.GetAsString(
|
desc = "<body style='background-color: {}; color: {}'>{}</body>".format(
|
||||||
wx.C2S_HTML_SYNTAX) + "' >" + desc + "</body>"
|
bgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
|
||||||
|
fgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
|
||||||
|
desc
|
||||||
|
)
|
||||||
|
|
||||||
self.description.SetPage(desc)
|
self.description.SetPage(desc)
|
||||||
|
|
||||||
|
|||||||
@@ -22,38 +22,67 @@ class ItemMutator(wx.Panel):
|
|||||||
self.item = item
|
self.item = item
|
||||||
self.timer = None
|
self.timer = None
|
||||||
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||||
|
|
||||||
|
font = parent.GetFont()
|
||||||
|
font.SetWeight(wx.BOLD)
|
||||||
|
|
||||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
|
sourceItemsSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons"), 0, wx.LEFT, 5)
|
||||||
|
sourceItemsSizer.Add(BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons"), 0, wx.LEFT, 0)
|
||||||
|
sourceItemShort = "{} {}".format(stuff.mutaplasmid.item.name.split(" ")[0], stuff.baseItem.name)
|
||||||
|
sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
|
||||||
|
sourceItemText.SetFont(font)
|
||||||
|
sourceItemsSizer.Add(sourceItemText, 0, wx.LEFT, 10)
|
||||||
|
mainSizer.Add(sourceItemsSizer, 0, wx.TOP | wx.EXPAND, 10)
|
||||||
|
|
||||||
|
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
self.goodColor = wx.Colour(96, 191, 0)
|
self.goodColor = wx.Colour(96, 191, 0)
|
||||||
self.badColor = wx.Colour(255, 64, 0)
|
self.badColor = wx.Colour(255, 64, 0)
|
||||||
|
|
||||||
self.event_mapping = {}
|
self.event_mapping = {}
|
||||||
|
|
||||||
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
|
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||||
# create array for the two ranges
|
# Format: [raw value, modifier applied to base raw value, display value]
|
||||||
min_t = [m.minValue, m.minMod, None]
|
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
|
||||||
max_t = [m.maxValue, m.maxMod, None]
|
range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue))
|
||||||
|
|
||||||
# Then we need to determine if it's better than original, which will be the color
|
# minValue/maxValue do not always correspond to min/max, because these are
|
||||||
min_t[2] = min_t[1] < 1 if not m.highIsGood else 1 < min_t[1]
|
# just base value multiplied by minMod/maxMod, and in case base is negative
|
||||||
max_t[2] = max_t[1] < 1 if not m.highIsGood else 1 < max_t[1]
|
# minValue is actually bigger than maxValue
|
||||||
|
if range1[0] <= range2[0]:
|
||||||
# Lastly, we need to determine which range value is "worse" (left side) or "better" (right side)
|
minRange = range1
|
||||||
if (m.highIsGood and min_t[1] > max_t[1]) or (not m.highIsGood and min_t[1] < max_t[1]):
|
maxRange = range2
|
||||||
better_range = min_t
|
|
||||||
else:
|
else:
|
||||||
better_range = max_t
|
minRange = range2
|
||||||
|
maxRange = range1
|
||||||
|
|
||||||
if (m.highIsGood and max_t[1] < min_t[1]) or (not m.highIsGood and max_t[1] > min_t[1]):
|
if (m.highIsGood and minRange[0] >= maxRange[0]) or (not m.highIsGood and minRange[0] <= maxRange[0]):
|
||||||
worse_range = max_t
|
betterRange = minRange
|
||||||
|
worseRange = maxRange
|
||||||
else:
|
else:
|
||||||
worse_range = min_t
|
betterRange = maxRange
|
||||||
|
worseRange = minRange
|
||||||
|
|
||||||
|
if minRange[1] >= maxRange[1]:
|
||||||
|
displayMaxRange = minRange
|
||||||
|
displayMinRange = maxRange
|
||||||
|
else:
|
||||||
|
displayMaxRange = maxRange
|
||||||
|
displayMinRange = minRange
|
||||||
|
|
||||||
|
# If base value is outside of mutation range, make sure that center of slider
|
||||||
|
# corresponds to the value which is closest available to actual base value. It's
|
||||||
|
# how EVE handles it
|
||||||
|
if minRange[0] <= m.baseValue <= maxRange[0]:
|
||||||
|
sliderBaseValue = m.baseValue
|
||||||
|
else:
|
||||||
|
sliderBaseValue = max(minRange[0], min(maxRange[0], m.baseValue))
|
||||||
|
|
||||||
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
|
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
|
||||||
font = parent.GetFont()
|
|
||||||
font.SetWeight(wx.BOLD)
|
|
||||||
|
|
||||||
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
|
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
|
||||||
|
|
||||||
displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName)
|
displayName = wx.StaticText(self, wx.ID_ANY, m.attribute.displayName)
|
||||||
@@ -61,25 +90,25 @@ class ItemMutator(wx.Panel):
|
|||||||
|
|
||||||
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
|
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
|
||||||
|
|
||||||
worst_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worse_range[0]), rounding='dec')
|
worseVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worseRange[0]), rounding='dec')
|
||||||
worst_text = wx.StaticText(self, wx.ID_ANY, worst_val)
|
worseText = wx.StaticText(self, wx.ID_ANY, worseVal)
|
||||||
worst_text.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
|
worseText.SetForegroundColour(self.badColor)
|
||||||
|
|
||||||
best_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(better_range[0]), rounding='dec')
|
betterVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(betterRange[0]), rounding='dec')
|
||||||
best_text = wx.StaticText(self, wx.ID_ANY, best_val)
|
betterText = wx.StaticText(self, wx.ID_ANY, betterVal)
|
||||||
best_text.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
|
betterText.SetForegroundColour(self.goodColor)
|
||||||
|
|
||||||
headingSizer.Add(worst_text, 0, wx.ALL | wx.EXPAND, 0)
|
headingSizer.Add(worseText, 0, wx.ALL | wx.EXPAND, 0)
|
||||||
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, " ─ "), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
|
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, " ─ "), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
|
||||||
headingSizer.Add(best_text, 0, wx.RIGHT | wx.EXPAND, 10)
|
headingSizer.Add(betterText, 0, wx.RIGHT | wx.EXPAND, 10)
|
||||||
|
|
||||||
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
|
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
slider = AttributeSlider(parent=self,
|
slider = AttributeSlider(parent=self,
|
||||||
baseValue=m.attribute.unit.SimplifyValue(m.baseValue),
|
baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue),
|
||||||
minValue=m.attribute.unit.SimplifyValue(min_t[0]),
|
minValue=displayMinRange[1],
|
||||||
maxValue=m.attribute.unit.SimplifyValue(max_t[0]),
|
maxValue=displayMaxRange[1],
|
||||||
inverse=better_range is min_t)
|
inverse=displayMaxRange is worseRange)
|
||||||
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
|
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
|
||||||
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
|
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
|
||||||
self.event_mapping[slider] = m
|
self.event_mapping[slider] = m
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import wx
|
import wx
|
||||||
import gui.utils.color as colorUtils
|
import gui.utils.color as colorUtils
|
||||||
import gui.utils.draw as drawUtils
|
import gui.utils.draw as drawUtils
|
||||||
|
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||||
|
|
||||||
SearchButton, EVT_SEARCH_BTN = wx.lib.newevent.NewEvent()
|
SearchButton, EVT_SEARCH_BTN = wx.lib.newevent.NewEvent()
|
||||||
CancelButton, EVT_CANCEL_BTN = wx.lib.newevent.NewEvent()
|
CancelButton, EVT_CANCEL_BTN = wx.lib.newevent.NewEvent()
|
||||||
@@ -55,7 +56,7 @@ class PFSearchBox(wx.Window):
|
|||||||
|
|
||||||
self.EditBox.Bind(wx.EVT_SET_FOCUS, self.OnEditSetFocus)
|
self.EditBox.Bind(wx.EVT_SET_FOCUS, self.OnEditSetFocus)
|
||||||
self.EditBox.Bind(wx.EVT_KILL_FOCUS, self.OnEditKillFocus)
|
self.EditBox.Bind(wx.EVT_KILL_FOCUS, self.OnEditKillFocus)
|
||||||
|
self.EditBox.Bind(wx.EVT_KEY_DOWN, self.OnKeyPress)
|
||||||
self.EditBox.Bind(wx.EVT_TEXT, self.OnText)
|
self.EditBox.Bind(wx.EVT_TEXT, self.OnText)
|
||||||
self.EditBox.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
|
self.EditBox.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter)
|
||||||
|
|
||||||
@@ -83,6 +84,12 @@ class PFSearchBox(wx.Window):
|
|||||||
self.Clear()
|
self.Clear()
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
def OnKeyPress(self, event):
|
||||||
|
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||||
|
HandleCtrlBackspace(self.EditBox)
|
||||||
|
else:
|
||||||
|
event.Skip()
|
||||||
|
|
||||||
def Clear(self):
|
def Clear(self):
|
||||||
self.EditBox.Clear()
|
self.EditBox.Clear()
|
||||||
# self.EditBox.ChangeValue(self.descriptiveText)
|
# self.EditBox.ChangeValue(self.descriptiveText)
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ class PFContextMenuPref(PreferenceView):
|
|||||||
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
|
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
|
||||||
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
|
self.rbBox7.Bind(wx.EVT_RADIOBOX, self.OnSetting7Change)
|
||||||
|
|
||||||
|
self.rbBox8 = wx.RadioBox(panel, -1, "Fill with module", wx.DefaultPosition, wx.DefaultSize, ['Disabled', 'Enabled'], 1, wx.RA_SPECIFY_COLS)
|
||||||
|
self.rbBox8.SetSelection(self.settings.get('moduleFill'))
|
||||||
|
rbSizerRow3.Add(self.rbBox8, 1, wx.TOP | wx.RIGHT, 5)
|
||||||
|
self.rbBox8.Bind(wx.EVT_RADIOBOX, self.OnSetting8Change)
|
||||||
|
|
||||||
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
|
mainSizer.Add(rbSizerRow3, 1, wx.ALL | wx.EXPAND, 0)
|
||||||
|
|
||||||
panel.SetSizer(mainSizer)
|
panel.SetSizer(mainSizer)
|
||||||
@@ -107,6 +112,9 @@ class PFContextMenuPref(PreferenceView):
|
|||||||
def OnSetting7Change(self, event):
|
def OnSetting7Change(self, event):
|
||||||
self.settings.set('project', event.GetInt())
|
self.settings.set('project', event.GetInt())
|
||||||
|
|
||||||
|
def OnSetting8Change(self, event):
|
||||||
|
self.settings.set('moduleFill', event.GetInt())
|
||||||
|
|
||||||
def getImage(self):
|
def getImage(self):
|
||||||
return BitmapLoader.getBitmap("settings_menu", "gui")
|
return BitmapLoader.getBitmap("settings_menu", "gui")
|
||||||
|
|
||||||
|
|||||||
@@ -76,10 +76,6 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.cbGaugeAnimation = wx.CheckBox(panel, wx.ID_ANY, "Animate gauges", wx.DefaultPosition, wx.DefaultSize, 0)
|
self.cbGaugeAnimation = wx.CheckBox(panel, wx.ID_ANY, "Animate gauges", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
mainSizer.Add(self.cbGaugeAnimation, 0, wx.ALL | wx.EXPAND, 5)
|
mainSizer.Add(self.cbGaugeAnimation, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
|
|
||||||
self.cbExportCharges = wx.CheckBox(panel, wx.ID_ANY, "Export loaded charges", wx.DefaultPosition,
|
|
||||||
wx.DefaultSize, 0)
|
|
||||||
mainSizer.Add(self.cbExportCharges, 0, wx.ALL | wx.EXPAND, 5)
|
|
||||||
|
|
||||||
self.cbOpenFitInNew = wx.CheckBox(panel, wx.ID_ANY, "Open fittings in a new page by default",
|
self.cbOpenFitInNew = wx.CheckBox(panel, wx.ID_ANY, "Open fittings in a new page by default",
|
||||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||||
mainSizer.Add(self.cbOpenFitInNew, 0, wx.ALL | wx.EXPAND, 5)
|
mainSizer.Add(self.cbOpenFitInNew, 0, wx.ALL | wx.EXPAND, 5)
|
||||||
@@ -133,7 +129,6 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.cbShowTooltip.SetValue(self.sFit.serviceFittingOptions["showTooltip"] or False)
|
self.cbShowTooltip.SetValue(self.sFit.serviceFittingOptions["showTooltip"] or False)
|
||||||
self.cbMarketShortcuts.SetValue(self.sFit.serviceFittingOptions["showMarketShortcuts"] or False)
|
self.cbMarketShortcuts.SetValue(self.sFit.serviceFittingOptions["showMarketShortcuts"] or False)
|
||||||
self.cbGaugeAnimation.SetValue(self.sFit.serviceFittingOptions["enableGaugeAnimation"])
|
self.cbGaugeAnimation.SetValue(self.sFit.serviceFittingOptions["enableGaugeAnimation"])
|
||||||
self.cbExportCharges.SetValue(self.sFit.serviceFittingOptions["exportCharges"])
|
|
||||||
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
|
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
|
||||||
self.chPriceSource.SetStringSelection(self.sFit.serviceFittingOptions["priceSource"])
|
self.chPriceSource.SetStringSelection(self.sFit.serviceFittingOptions["priceSource"])
|
||||||
self.chPriceSystem.SetStringSelection(self.sFit.serviceFittingOptions["priceSystem"])
|
self.chPriceSystem.SetStringSelection(self.sFit.serviceFittingOptions["priceSystem"])
|
||||||
@@ -151,7 +146,6 @@ class PFGeneralPref(PreferenceView):
|
|||||||
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
|
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
|
||||||
self.cbMarketShortcuts.Bind(wx.EVT_CHECKBOX, self.onCBShowShortcuts)
|
self.cbMarketShortcuts.Bind(wx.EVT_CHECKBOX, self.onCBShowShortcuts)
|
||||||
self.cbGaugeAnimation.Bind(wx.EVT_CHECKBOX, self.onCBGaugeAnimation)
|
self.cbGaugeAnimation.Bind(wx.EVT_CHECKBOX, self.onCBGaugeAnimation)
|
||||||
self.cbExportCharges.Bind(wx.EVT_CHECKBOX, self.onCBExportCharges)
|
|
||||||
self.cbOpenFitInNew.Bind(wx.EVT_CHECKBOX, self.onCBOpenFitInNew)
|
self.cbOpenFitInNew.Bind(wx.EVT_CHECKBOX, self.onCBOpenFitInNew)
|
||||||
self.chPriceSource.Bind(wx.EVT_CHOICE, self.onPricesSourceSelection)
|
self.chPriceSource.Bind(wx.EVT_CHOICE, self.onPricesSourceSelection)
|
||||||
self.chPriceSystem.Bind(wx.EVT_CHOICE, self.onPriceSelection)
|
self.chPriceSystem.Bind(wx.EVT_CHOICE, self.onPriceSelection)
|
||||||
@@ -220,9 +214,6 @@ class PFGeneralPref(PreferenceView):
|
|||||||
def onCBGaugeAnimation(self, event):
|
def onCBGaugeAnimation(self, event):
|
||||||
self.sFit.serviceFittingOptions["enableGaugeAnimation"] = self.cbGaugeAnimation.GetValue()
|
self.sFit.serviceFittingOptions["enableGaugeAnimation"] = self.cbGaugeAnimation.GetValue()
|
||||||
|
|
||||||
def onCBExportCharges(self, event):
|
|
||||||
self.sFit.serviceFittingOptions["exportCharges"] = self.cbExportCharges.GetValue()
|
|
||||||
|
|
||||||
def onCBOpenFitInNew(self, event):
|
def onCBOpenFitInNew(self, event):
|
||||||
self.sFit.serviceFittingOptions["openFitInNew"] = self.cbOpenFitInNew.GetValue()
|
self.sFit.serviceFittingOptions["openFitInNew"] = self.cbOpenFitInNew.GetValue()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import gui.utils.fonts as fonts
|
|||||||
from .events import FitSelected, SearchSelected, ImportSelected, Stage1Selected, Stage2Selected, Stage3Selected
|
from .events import FitSelected, SearchSelected, ImportSelected, Stage1Selected, Stage2Selected, Stage3Selected
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
|
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||||
|
|
||||||
pyfalog = Logger(__name__)
|
pyfalog = Logger(__name__)
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
|||||||
|
|
||||||
# self.BrowserSearchBox.Bind(wx.EVT_TEXT_ENTER, self.OnBrowserSearchBoxEnter)
|
# self.BrowserSearchBox.Bind(wx.EVT_TEXT_ENTER, self.OnBrowserSearchBoxEnter)
|
||||||
# self.BrowserSearchBox.Bind(wx.EVT_KILL_FOCUS, self.OnBrowserSearchBoxLostFocus)
|
# self.BrowserSearchBox.Bind(wx.EVT_KILL_FOCUS, self.OnBrowserSearchBoxLostFocus)
|
||||||
self.BrowserSearchBox.Bind(wx.EVT_KEY_DOWN, self.OnBrowserSearchBoxEsc)
|
self.BrowserSearchBox.Bind(wx.EVT_KEY_DOWN, self.OnBrowserSearchBoxKeyPress)
|
||||||
self.BrowserSearchBox.Bind(wx.EVT_TEXT, self.OnScheduleSearch)
|
self.BrowserSearchBox.Bind(wx.EVT_TEXT, self.OnScheduleSearch)
|
||||||
|
|
||||||
self.SetMinSize(size)
|
self.SetMinSize(size)
|
||||||
@@ -103,9 +104,11 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
|||||||
def OnBrowserSearchBoxLostFocus(self, event):
|
def OnBrowserSearchBoxLostFocus(self, event):
|
||||||
self.BrowserSearchBox.Show(False)
|
self.BrowserSearchBox.Show(False)
|
||||||
|
|
||||||
def OnBrowserSearchBoxEsc(self, event):
|
def OnBrowserSearchBoxKeyPress(self, event):
|
||||||
if event.GetKeyCode() == wx.WXK_ESCAPE:
|
if event.GetKeyCode() == wx.WXK_ESCAPE:
|
||||||
self.BrowserSearchBox.Show(False)
|
self.BrowserSearchBox.Show(False)
|
||||||
|
elif event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||||
|
HandleCtrlBackspace(self.BrowserSearchBox)
|
||||||
else:
|
else:
|
||||||
event.Skip()
|
event.Skip()
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import wx
|
|||||||
import gui.mainFrame
|
import gui.mainFrame
|
||||||
from gui.statsView import StatsView
|
from gui.statsView import StatsView
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||||
|
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||||
from service.fit import Fit
|
from service.fit import Fit
|
||||||
|
|
||||||
|
|
||||||
@@ -148,27 +149,55 @@ class FirepowerViewFull(StatsView):
|
|||||||
else:
|
else:
|
||||||
self.stEff.Hide()
|
self.stEff.Hide()
|
||||||
|
|
||||||
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS", None),
|
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest):
|
||||||
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
|
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||||
("labelFullVolleyTotal", lambda: fit.totalVolley, 3, 0, 0, "%s", "Volley: %.1f"),
|
return ""
|
||||||
("labelFullDpsTotal", lambda: fit.totalDPS, 3, 0, 0, "%s", None))
|
else:
|
||||||
# See GH issue #
|
return "Spool up: {}-{}".format(
|
||||||
# if fit is not None and fit.totalYield > 0:
|
formatAmount(preSpool, prec, lowest, highest),
|
||||||
# self.miningyield.Show()
|
formatAmount(fullSpool, prec, lowest, highest))
|
||||||
# else:
|
|
||||||
# self.miningyield.Hide()
|
# TODO: fetch spoolup option
|
||||||
|
defaultSpoolValue = 1
|
||||||
|
stats = (
|
||||||
|
(
|
||||||
|
"labelFullDpsWeapon",
|
||||||
|
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||||
|
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||||
|
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||||
|
3, 0, 0, "{}{} DPS"),
|
||||||
|
(
|
||||||
|
"labelFullDpsDrone",
|
||||||
|
lambda: fit.getDroneDps().total,
|
||||||
|
lambda: fit.getDroneDps().total,
|
||||||
|
lambda: fit.getDroneDps().total,
|
||||||
|
3, 0, 0, "{}{} DPS"),
|
||||||
|
(
|
||||||
|
"labelFullVolleyTotal",
|
||||||
|
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||||
|
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||||
|
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||||
|
3, 0, 0, "{}{}"),
|
||||||
|
(
|
||||||
|
"labelFullDpsTotal",
|
||||||
|
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total,
|
||||||
|
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).total,
|
||||||
|
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).total,
|
||||||
|
3, 0, 0, "{}{}"))
|
||||||
|
|
||||||
counter = 0
|
counter = 0
|
||||||
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
|
for labelName, val, preSpoolVal, fullSpoolVal, prec, lowest, highest, valueFormat in stats:
|
||||||
label = getattr(self, labelName)
|
label = getattr(self, labelName)
|
||||||
value = value() if fit is not None else 0
|
val = val() if fit is not None else 0
|
||||||
value = value if value is not None else 0
|
preSpoolVal = preSpoolVal() if fit is not None else 0
|
||||||
if self._cachedValues[counter] != value:
|
fullSpoolVal = fullSpoolVal() if fit is not None else 0
|
||||||
valueStr = formatAmount(value, prec, lowest, highest)
|
if self._cachedValues[counter] != val:
|
||||||
label.SetLabel(valueFormat % valueStr)
|
tooltipText = dpsToolTip(preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
label.SetLabel(valueFormat.format(
|
||||||
label.SetToolTip(wx.ToolTip(tipStr))
|
formatAmount(val, prec, lowest, highest),
|
||||||
self._cachedValues[counter] = value
|
"\u02e2" if tooltipText else ""))
|
||||||
|
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||||
|
self._cachedValues[counter] = val
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
self.panel.Layout()
|
self.panel.Layout()
|
||||||
|
|||||||
@@ -21,7 +21,35 @@
|
|||||||
import wx
|
import wx
|
||||||
from gui.statsView import StatsView
|
from gui.statsView import StatsView
|
||||||
from gui.bitmap_loader import BitmapLoader
|
from gui.bitmap_loader import BitmapLoader
|
||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||||
|
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||||
|
|
||||||
|
|
||||||
|
stats = [
|
||||||
|
(
|
||||||
|
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Capacitor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Capacitor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Capacitor", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Shield", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Shield", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Shield", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Armor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Armor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Armor", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Hull", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Hull", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Hull", 0),
|
||||||
|
3, 0, 0)]
|
||||||
|
|
||||||
|
|
||||||
class OutgoingViewFull(StatsView):
|
class OutgoingViewFull(StatsView):
|
||||||
@@ -48,56 +76,46 @@ class OutgoingViewFull(StatsView):
|
|||||||
|
|
||||||
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
||||||
|
|
||||||
counter = 0
|
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||||
|
|
||||||
rr_list = [
|
|
||||||
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
|
|
||||||
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
|
|
||||||
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
|
|
||||||
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
|
|
||||||
]
|
|
||||||
|
|
||||||
for outgoingType, label, image, tooltip in rr_list:
|
|
||||||
baseBox = wx.BoxSizer(wx.VERTICAL)
|
baseBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
|
baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER)
|
||||||
|
|
||||||
if "Capacitor" in outgoingType:
|
lbl = wx.StaticText(parent, wx.ID_ANY, valueFormat.format(0, ""))
|
||||||
lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s")
|
|
||||||
else:
|
|
||||||
lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s")
|
|
||||||
|
|
||||||
lbl.SetToolTip(wx.ToolTip(tooltip))
|
lbl.SetToolTip(wx.ToolTip(tooltip))
|
||||||
|
setattr(self, labelName, lbl)
|
||||||
setattr(self, "label%s" % outgoingType, lbl)
|
|
||||||
|
|
||||||
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
||||||
self._cachedValues.append(0)
|
self._cachedValues.append(0)
|
||||||
counter += 1
|
|
||||||
|
|
||||||
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
||||||
|
|
||||||
def refreshPanel(self, fit):
|
def refreshPanel(self, fit):
|
||||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
|
||||||
|
|
||||||
stats = [
|
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
|
||||||
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
|
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||||
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
|
return False, text
|
||||||
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
|
else:
|
||||||
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
|
return True, "{}\nSpool up: {}-{}".format(
|
||||||
]
|
text,
|
||||||
|
formatAmount(preSpool, prec, lowest, highest),
|
||||||
|
formatAmount(fullSpool, prec, lowest, highest))
|
||||||
|
|
||||||
|
# TODO: fetch spoolup option
|
||||||
|
defaultSpoolValue = 1
|
||||||
counter = 0
|
counter = 0
|
||||||
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
|
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||||
label = getattr(self, labelName)
|
label = getattr(self, labelName)
|
||||||
value = value() if fit is not None else 0
|
val = val(fit, defaultSpoolValue) if fit is not None else 0
|
||||||
value = value if value is not None else 0
|
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
|
||||||
if self._cachedValues[counter] != value:
|
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
|
||||||
valueStr = formatAmount(value, prec, lowest, highest)
|
if self._cachedValues[counter] != val:
|
||||||
label.SetLabel(valueFormat % valueStr)
|
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
label.SetLabel(valueFormat.format(
|
||||||
label.SetToolTip(wx.ToolTip(tipStr))
|
formatAmount(val, prec, lowest, highest),
|
||||||
self._cachedValues[counter] = value
|
"\u02e2" if hasSpool else ""))
|
||||||
|
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||||
|
self._cachedValues[counter] = val
|
||||||
counter += 1
|
counter += 1
|
||||||
self.panel.Layout()
|
self.panel.Layout()
|
||||||
self.headerPanel.Layout()
|
self.headerPanel.Layout()
|
||||||
|
|||||||
@@ -20,7 +20,35 @@
|
|||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
import wx
|
import wx
|
||||||
from gui.statsView import StatsView
|
from gui.statsView import StatsView
|
||||||
from gui.utils.numberFormatter import formatAmount
|
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||||
|
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||||
|
|
||||||
|
|
||||||
|
stats = [
|
||||||
|
(
|
||||||
|
"labelRemoteCapacitor", "Capacitor:", "{}{} GJ/s", "capacitorInfo", "Capacitor restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Capacitor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Capacitor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Capacitor", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteShield", "Shield:", "{}{} HP/s", "shieldActive", "Shield restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Shield", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Shield", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Shield", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteArmor", "Armor:", "{}{} HP/s", "armorActive", "Armor restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Armor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Armor", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Armor", 0),
|
||||||
|
3, 0, 0),
|
||||||
|
(
|
||||||
|
"labelRemoteHull", "Hull:", "{}{} HP/s", "hullActive", "Hull restored",
|
||||||
|
lambda fit, spool: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, spool, False)).get("Hull", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True)).get("Hull", 0),
|
||||||
|
lambda fit: fit.getRemoteReps(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True)).get("Hull", 0),
|
||||||
|
3, 0, 0)]
|
||||||
|
|
||||||
|
|
||||||
class OutgoingViewMinimal(StatsView):
|
class OutgoingViewMinimal(StatsView):
|
||||||
@@ -47,56 +75,46 @@ class OutgoingViewMinimal(StatsView):
|
|||||||
|
|
||||||
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
||||||
|
|
||||||
counter = 0
|
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||||
|
|
||||||
rr_list = [
|
|
||||||
("RemoteCapacitor", "Capacitor:", "capacitorInfo", "Capacitor GJ/s per second transferred remotely."),
|
|
||||||
("RemoteShield", "Shield:", "shieldActive", "Shield hitpoints per second repaired remotely."),
|
|
||||||
("RemoteArmor", "Armor:", "armorActive", "Armor hitpoints per second repaired remotely."),
|
|
||||||
("RemoteHull", "Hull:", "hullActive", "Hull hitpoints per second repaired remotely."),
|
|
||||||
]
|
|
||||||
|
|
||||||
for outgoingType, label, image, tooltip in rr_list:
|
|
||||||
baseBox = wx.BoxSizer(wx.VERTICAL)
|
baseBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
|
||||||
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 0, wx.ALIGN_CENTER)
|
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, labelDesc), 0, wx.ALIGN_CENTER)
|
||||||
|
|
||||||
if "Capacitor" in outgoingType:
|
|
||||||
lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s")
|
|
||||||
else:
|
|
||||||
lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s")
|
|
||||||
|
|
||||||
|
lbl = wx.StaticText(parent, wx.ID_ANY, valueFormat.format(0, ""))
|
||||||
lbl.SetToolTip(wx.ToolTip(tooltip))
|
lbl.SetToolTip(wx.ToolTip(tooltip))
|
||||||
|
setattr(self, labelName, lbl)
|
||||||
setattr(self, "label%s" % outgoingType, lbl)
|
|
||||||
|
|
||||||
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
||||||
self._cachedValues.append(0)
|
self._cachedValues.append(0)
|
||||||
counter += 1
|
|
||||||
|
|
||||||
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
||||||
|
|
||||||
def refreshPanel(self, fit):
|
def refreshPanel(self, fit):
|
||||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
|
||||||
|
|
||||||
stats = [
|
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
|
||||||
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
|
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||||
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
|
return False, text
|
||||||
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
|
else:
|
||||||
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
|
return True, "{}\nSpool up: {}-{}".format(
|
||||||
]
|
text,
|
||||||
|
formatAmount(preSpool, prec, lowest, highest),
|
||||||
|
formatAmount(fullSpool, prec, lowest, highest))
|
||||||
|
|
||||||
|
# TODO: fetch spoolup option
|
||||||
|
defaultSpoolValue = 1
|
||||||
counter = 0
|
counter = 0
|
||||||
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
|
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||||
label = getattr(self, labelName)
|
label = getattr(self, labelName)
|
||||||
value = value() if fit is not None else 0
|
val = val(fit, defaultSpoolValue) if fit is not None else 0
|
||||||
value = value if value is not None else 0
|
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
|
||||||
if self._cachedValues[counter] != value:
|
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
|
||||||
valueStr = formatAmount(value, prec, lowest, highest)
|
if self._cachedValues[counter] != val:
|
||||||
label.SetLabel(valueFormat % valueStr)
|
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
label.SetLabel(valueFormat.format(
|
||||||
label.SetToolTip(wx.ToolTip(tipStr))
|
formatAmount(val, prec, lowest, highest),
|
||||||
self._cachedValues[counter] = value
|
"\u02e2" if hasSpool else ""))
|
||||||
|
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||||
|
self._cachedValues[counter] = val
|
||||||
counter += 1
|
counter += 1
|
||||||
self.panel.Layout()
|
self.panel.Layout()
|
||||||
self.headerPanel.Layout()
|
self.headerPanel.Layout()
|
||||||
|
|||||||
@@ -63,15 +63,20 @@ class RechargeViewFull(StatsView):
|
|||||||
|
|
||||||
# Add an empty label first for correct alignment.
|
# Add an empty label first for correct alignment.
|
||||||
sizerTankStats.Add(wx.StaticText(contentPanel, wx.ID_ANY, ""), 0)
|
sizerTankStats.Add(wx.StaticText(contentPanel, wx.ID_ANY, ""), 0)
|
||||||
toolTipText = {"shieldPassive": "Passive shield recharge", "shieldActive": "Active shield boost",
|
toolTipText = {
|
||||||
"armorActive": "Armor repair amount", "hullActive": "Hull repair amount"}
|
"shieldPassive": "Passive shield recharge",
|
||||||
|
"shieldActive": "Active shield boost",
|
||||||
|
"armorActive": "Armor repair amount",
|
||||||
|
"hullActive": "Hull repair amount"}
|
||||||
for tankType in ("shieldPassive", "shieldActive", "armorActive", "hullActive"):
|
for tankType in ("shieldPassive", "shieldActive", "armorActive", "hullActive"):
|
||||||
bitmap = BitmapLoader.getStaticBitmap("%s_big" % tankType, contentPanel, "gui")
|
bitmap = BitmapLoader.getStaticBitmap("%s_big" % tankType, contentPanel, "gui")
|
||||||
tooltip = wx.ToolTip(toolTipText[tankType])
|
tooltip = wx.ToolTip(toolTipText[tankType])
|
||||||
bitmap.SetToolTip(tooltip)
|
bitmap.SetToolTip(tooltip)
|
||||||
sizerTankStats.Add(bitmap, 0, wx.ALIGN_CENTER)
|
sizerTankStats.Add(bitmap, 0, wx.ALIGN_CENTER)
|
||||||
|
|
||||||
toolTipText = {"reinforced": "Reinforced", "sustained": "Sustained"}
|
toolTipText = {
|
||||||
|
"reinforced": "Reinforced",
|
||||||
|
"sustained": "Sustained"}
|
||||||
for stability in ("reinforced", "sustained"):
|
for stability in ("reinforced", "sustained"):
|
||||||
bitmap = BitmapLoader.getStaticBitmap("regen%s_big" % stability.capitalize(), contentPanel, "gui")
|
bitmap = BitmapLoader.getStaticBitmap("regen%s_big" % stability.capitalize(), contentPanel, "gui")
|
||||||
tooltip = wx.ToolTip(toolTipText[stability])
|
tooltip = wx.ToolTip(toolTipText[stability])
|
||||||
@@ -85,7 +90,6 @@ class RechargeViewFull(StatsView):
|
|||||||
tankTypeCap = tankType[0].capitalize() + tankType[1:]
|
tankTypeCap = tankType[0].capitalize() + tankType[1:]
|
||||||
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0.0", style=wx.ALIGN_RIGHT)
|
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0.0", style=wx.ALIGN_RIGHT)
|
||||||
setattr(self, "labelTank%s%s" % (stability.capitalize(), tankTypeCap), lbl)
|
setattr(self, "labelTank%s%s" % (stability.capitalize(), tankTypeCap), lbl)
|
||||||
|
|
||||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
box = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
box.Add(lbl, 0, wx.EXPAND)
|
box.Add(lbl, 0, wx.EXPAND)
|
||||||
|
|
||||||
@@ -115,9 +119,23 @@ class RechargeViewFull(StatsView):
|
|||||||
unitlbl = getattr(self, "unitLabelTank%s%sActive" % (stability.capitalize(), name.capitalize()))
|
unitlbl = getattr(self, "unitLabelTank%s%sActive" % (stability.capitalize(), name.capitalize()))
|
||||||
unitlbl.SetLabel(unit)
|
unitlbl.SetLabel(unit)
|
||||||
if tank is not None:
|
if tank is not None:
|
||||||
lbl.SetLabel("%.1f" % tank["%sRepair" % name])
|
amount = tank["{}Repair".format(name)]
|
||||||
else:
|
else:
|
||||||
lbl.SetLabel("0.0")
|
amount = 0
|
||||||
|
|
||||||
|
if tank is not None and name == "armor":
|
||||||
|
preSpoolAmount = tank["armorRepairPreSpool"]
|
||||||
|
fullSpoolAmount = tank["armorRepairFullSpool"]
|
||||||
|
if round(preSpoolAmount, 1) != round(fullSpoolAmount, 1):
|
||||||
|
ttText = "Spool up: {:.1f}-{:.1f}".format(preSpoolAmount, fullSpoolAmount)
|
||||||
|
else:
|
||||||
|
ttText = ""
|
||||||
|
else:
|
||||||
|
ttText = ""
|
||||||
|
|
||||||
|
lbl.SetLabel("{:.1f}{}".format(amount, "\u02e2" if ttText else ""))
|
||||||
|
lbl.SetToolTip(wx.ToolTip(ttText))
|
||||||
|
unitlbl.SetToolTip(wx.ToolTip(ttText))
|
||||||
|
|
||||||
if fit is not None:
|
if fit is not None:
|
||||||
label = getattr(self, "labelTankSustainedShieldPassive")
|
label = getattr(self, "labelTankSustainedShieldPassive")
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user