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:
|
||||
|
||||
global:
|
||||
# SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
|
||||
# /E:ON and /V:ON options are not enabled in the batch script intepreter
|
||||
@@ -8,76 +7,11 @@ environment:
|
||||
|
||||
matrix:
|
||||
|
||||
# Python 2.7.10 is the latest version and is not pre-installed.
|
||||
|
||||
- PYTHON: "C:\\Python27.10"
|
||||
PYTHON_VERSION: "2.7.10"
|
||||
- PYTHON: "C:\\Python36"
|
||||
PYTHON_VERSION: "3.6.x"
|
||||
PYTHON_ARCH: "32"
|
||||
|
||||
#- PYTHON: "C:\\Python27.10-x64"
|
||||
# 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"
|
||||
|
||||
init:
|
||||
- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
install:
|
||||
# If there is a newer build queued for the same PR, cancel this one.
|
||||
# The AppVeyor 'rollout builds' option is supposed to serve the same
|
||||
@@ -89,34 +23,23 @@ install:
|
||||
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
|
||||
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:"
|
||||
- ps: "ls \"C:/\""
|
||||
|
||||
- ECHO "Filesystem projects root:"
|
||||
- ps: "ls \"C:\\projects\\\""
|
||||
|
||||
- ECHO "Filesystem pyfa root:"
|
||||
- ps: "ls \"C:\\projects\\pyfa\\\""
|
||||
- ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\""
|
||||
|
||||
- ECHO "Installed SDKs:"
|
||||
- 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
|
||||
# done from inside the powershell script as it would require to restart
|
||||
# the parent CMD process).
|
||||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
|
||||
# Check that we have the expected version and architecture for Python
|
||||
- "python --version"
|
||||
- "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,
|
||||
# pip will build them from source using the MSVC compiler matching the
|
||||
# target Python version and architecture
|
||||
# C:\\projects\\eve-gnosis\\
|
||||
- ECHO "Install pip requirements:"
|
||||
- "pip install -r requirements.txt"
|
||||
- "pip install -r requirements_test.txt"
|
||||
- "pip install -r requirements_build_windows.txt"
|
||||
- "pip install PyInstaller"
|
||||
|
||||
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 the compiled extension
|
||||
# - "python setup.py build"
|
||||
- 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):"
|
||||
#- 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"
|
||||
@@ -150,12 +90,11 @@ build: on
|
||||
after_build:
|
||||
- ps: "ls \"./\""
|
||||
#- ps: "ls \"C:\\projects\\pyfa\\build\\pyfa\\\""
|
||||
- ps: "ls \"C:\\projects\\pyfa\\build\\\""
|
||||
- ps: "ls \"C:\\projects\\pyfa\\build\\exe.win32-2.7\\\""
|
||||
# - ps: "ls \"C:\\projects\\$env:APPVEYOR_PROJECT_SLUG\\build\\exe.win32-2.7\\\""
|
||||
# Zip
|
||||
# APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER
|
||||
#- 7z a build.zip -r C:\projects\pyfa\build\pyfa\*.*
|
||||
- 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\*.*
|
||||
|
||||
on_success:
|
||||
@@ -176,11 +115,21 @@ after_test:
|
||||
|
||||
artifacts:
|
||||
# Archive the generated packages in the ci.appveyor.com build report.
|
||||
- path: pyfa.zip
|
||||
name: 'pyfa.zip'
|
||||
- path: pyfa*-win.zip
|
||||
- path: pyfa*-win.exe
|
||||
#- path: pyfa_debug.zip
|
||||
# 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:
|
||||
# - TODO: upload the content of dist/*.whl to a public wheelhouse
|
||||
#
|
||||
59
.travis.yml
59
.travis.yml
@@ -1,36 +1,29 @@
|
||||
dist: trusty
|
||||
sudo: required
|
||||
os: linux
|
||||
language: python
|
||||
cache: pip
|
||||
python:
|
||||
- '3.6'
|
||||
env:
|
||||
- TOXENV=pep8
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- 3.6
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode7.3
|
||||
language: generic
|
||||
env: PYTHON=3.6.1
|
||||
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
|
||||
- pip install tox
|
||||
# We're not actually installing Tox, but have to run it before we install wxPython via Conda. This is fugly but vOv
|
||||
- tox
|
||||
- pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk2/ubuntu-14.04 wxPython==4.0.0b2
|
||||
# # get Conda
|
||||
# - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then
|
||||
# wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
|
||||
# else
|
||||
# wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;
|
||||
# fi
|
||||
# - bash miniconda.sh -b -p $HOME/miniconda
|
||||
# - export PATH="$HOME/miniconda/bin:$PATH"
|
||||
# - hash -r
|
||||
# - conda config --set always_yes yes --set changeps1 no
|
||||
# - conda update -q conda
|
||||
# # Useful for debugging any issues with conda
|
||||
# - conda info -a
|
||||
#install:
|
||||
# install wxPython 3.0.0.0
|
||||
# - conda install -c https://conda.anaconda.org/travis wxpython=4.0.0b2
|
||||
script:
|
||||
- tox
|
||||
|
||||
- bash scripts/setup-osx.sh
|
||||
install:
|
||||
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
|
||||
- bash scripts/package-osx.sh
|
||||
before_deploy:
|
||||
- export RELEASE_PKG_FILE=$(ls *.deb)
|
||||
- echo "deploying $RELEASE_PKG_FILE to GitHub releases"
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: Xfu0xApoB0zUPLXl29aYUulVC3iA4/3bXQwwADKCfAKZwxgNon4dLbO7Rie5/7Ukf2POL0KwmRaQGN3kOr+XSoIVTE4M5sXxnhiaaLGKQ+48hDizLE6JuXcZGJvkxUaghaTzIdCwHsG7VGBsPfQgfGsjJcfBp8tFNLmRyM/Jpsr8T6BR2MxtBIEUVy8zrOWFNZqnmWrY2pWMsB9fYt3JFNdpqeIgRAYqbBsBcZQ1MngLTi3ztuYS5IaF+lk06RrnBlHmUsJu/5nCvIpvPvD0i2BLZ3Uu0+Fn+8QWUgjJEL9MNseXZMXynu05xd8YRk7Ajc9CUrzQIIbAktyteYp85kE3pUJHmrMLcXhh7nqkwttR5/47Zwa3OLJLJFKBxMx6wY5jFkJjkV08850B7aWrmTFl/Eqc3Q5nZMuiEt3wFRbjxHi9h1mTN/fkxfRRHg8u3ENGPR+ZPiFC3J18qtks/B/hsKjjHvZP1i79OYlET4V/zyLyyQkCbpDaARQANuotLYJyZ7tH+KWEyRsvTi0M9Yev9mNNw6aI4vzh4HfkEhvcvnWnYwckPj1dnjQ573Qpw0Z9wsconoWfHAn+hBDt3+YLMrrFZl++mCRskHH1mZChX3aGMDi49zD0kfxBUkYPOAhguc6PwudBxHUZP+O6T/SoHylff6EizCE/k5dGeAk=
|
||||
file_glob: true
|
||||
file: "dist/pyfa-*.zip"
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
tags: true
|
||||
repo: pyfa-org/Pyfa
|
||||
|
||||
23
config.py
23
config.py
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \
|
||||
StreamHandler, TimedRotatingFileHandler, WARNING
|
||||
@@ -22,12 +23,6 @@ debug = False
|
||||
# Defines if our saveddata will be in pyfa root or not
|
||||
saveInRoot = False
|
||||
|
||||
# Version data
|
||||
|
||||
version = "2.6.1"
|
||||
tag = "Stable"
|
||||
expansionName = "Onslaught"
|
||||
expansionVersion = "1.5"
|
||||
evemonMinVersion = "4081"
|
||||
|
||||
minItemSearchLength = 3
|
||||
@@ -79,12 +74,7 @@ def getPyfaRoot():
|
||||
|
||||
|
||||
def getVersion():
|
||||
if os.path.isfile(os.path.join(pyfaPath, '.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)"
|
||||
return version
|
||||
|
||||
|
||||
def getDefaultSave():
|
||||
@@ -96,11 +86,12 @@ def defPaths(customSavePath=None):
|
||||
global pyfaPath
|
||||
global savePath
|
||||
global saveDB
|
||||
global gameDB
|
||||
global gameDB
|
||||
global saveInRoot
|
||||
global logPath
|
||||
global cipher
|
||||
global clientHash
|
||||
global version
|
||||
|
||||
pyfalog.debug("Configuring Pyfa")
|
||||
|
||||
@@ -110,6 +101,12 @@ def defPaths(customSavePath=None):
|
||||
if pyfaPath is None:
|
||||
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
|
||||
if saveInRoot is True:
|
||||
savePath = getattr(configforced, "savePath", None)
|
||||
|
||||
@@ -24,11 +24,13 @@ added_files = [
|
||||
('../../eve.db', '.'),
|
||||
('../../README.md', '.'),
|
||||
('../../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")
|
||||
|
||||
@@ -54,8 +56,10 @@ a = Analysis([r'../../pyfa.py'],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
@@ -70,10 +74,16 @@ exe = EXE(pyz,
|
||||
icon=icon,
|
||||
)
|
||||
|
||||
app = BUNDLE(exe,
|
||||
name='pyfa.app',
|
||||
icon=icon,
|
||||
bundle_identifier=None,
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True'
|
||||
})
|
||||
app = BUNDLE(
|
||||
exe,
|
||||
name='pyfa.app',
|
||||
icon=icon,
|
||||
bundle_identifier=None,
|
||||
info_plist={
|
||||
'NSHighResolutionCapable': 'True',
|
||||
'NSPrincipalClass': 'NSApplication',
|
||||
'CFBundleName': 'pyfa',
|
||||
'CFBundleDisplayName': 'pyfa',
|
||||
'CFBundleIdentifier': 'org.pyfaorg.pyfa',
|
||||
}
|
||||
)
|
||||
@@ -3,44 +3,35 @@
|
||||
import os.path
|
||||
from subprocess import call
|
||||
import zipfile
|
||||
from packaging.version import Version
|
||||
import yaml
|
||||
|
||||
|
||||
def zipdir(path, zip):
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
zip.write(os.path.join(root, file))
|
||||
with open("version.yml", 'r') as file:
|
||||
data = yaml.load(file)
|
||||
version = data['version']
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
fileName = "pyfa-{}-win".format(os.environ["PYFA_VERSION"])
|
||||
|
||||
print("Compiling EXE")
|
||||
|
||||
expansion = "%s %s" % (config['expansionName'], config['expansionVersion']),
|
||||
v = Version(version)
|
||||
|
||||
print(v)
|
||||
|
||||
call([
|
||||
iscc,
|
||||
os.path.join(os.getcwd(), "dist_assets", "win", "pyfa-setup.iss"),
|
||||
"/dMyAppVersion=%s" % (config['version']),
|
||||
"/dMyAppExpansion=%s" % expansion,
|
||||
"/dMyAppVersion=%s" % v,
|
||||
"/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
|
||||
|
||||
print("Done")
|
||||
|
||||
@@ -7,15 +7,12 @@
|
||||
#ifndef MyAppVersion
|
||||
#define MyAppVersion "2.1.0"
|
||||
#endif
|
||||
#ifndef MyAppExpansion
|
||||
#define MyAppExpansion "Vanguard 1.0"
|
||||
#endif
|
||||
|
||||
; Other config
|
||||
|
||||
#define MyAppName "pyfa"
|
||||
#define MyAppPublisher "pyfa"
|
||||
#define MyAppURL "https://forums.eveonline.com/t/27156"
|
||||
#define MyAppURL "https://github.com/pyfa-org/Pyfa/"
|
||||
#define MyAppExeName "pyfa.exe"
|
||||
|
||||
; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup
|
||||
@@ -23,7 +20,7 @@
|
||||
#define MinorVersionFlag 0
|
||||
|
||||
#ifndef MyOutputFile
|
||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-'+MyAppExpansion+'-win-wx3', " ", "-"))
|
||||
#define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-win', " ", "-"))
|
||||
#endif
|
||||
#ifndef MyAppDir
|
||||
#define MyAppDir "pyfa"
|
||||
@@ -39,7 +36,7 @@
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{3DA39096-C08D-49CD-90E0-1D177F32C8AA}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion} ({#MyAppExpansion})
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
@@ -51,10 +48,8 @@ LicenseFile={#MyAppDir}\LICENSE
|
||||
OutputDir={#MyOutputDir}
|
||||
OutputBaseFilename={#MyOutputFile}
|
||||
SetupIconFile={#MyAppDir}\pyfa.ico
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
CloseApplications=yes
|
||||
AppReadmeFile=https://github.com/pyfa-org/Pyfa/blob/v{#MyAppVersion}/readme.txt
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@@ -5,8 +5,7 @@ from itertools import chain
|
||||
import subprocess
|
||||
import requests.certs
|
||||
|
||||
label = subprocess.check_output([
|
||||
"git", "describe", "--tags"]).strip()
|
||||
label = subprocess.check_output(["git", "describe", "--tags"]).strip()
|
||||
|
||||
with open('.version', 'w+') as f:
|
||||
f.write(label.decode())
|
||||
@@ -18,7 +17,7 @@ added_files = [
|
||||
('../../imgs/gui/*.gif', 'imgs/gui'),
|
||||
('../../imgs/icons/*.png', 'imgs/icons'),
|
||||
('../../imgs/renders/*.png', 'imgs/renders'),
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../dist_assets/win/pyfa.ico', '.'),
|
||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||
@@ -26,10 +25,12 @@ added_files = [
|
||||
('../../eve.db', '.'),
|
||||
('../../README.md', '.'),
|
||||
('../../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
|
||||
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("iconID", 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 .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/>.
|
||||
# ===============================================================================
|
||||
|
||||
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 import relation, mapper
|
||||
import datetime
|
||||
@@ -40,6 +40,8 @@ modules_table = Table("modules", saveddata_meta,
|
||||
Column("position", Integer),
|
||||
Column("created", DateTime, nullable=True, default=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"'))
|
||||
|
||||
mapper(Module, modules_table,
|
||||
|
||||
@@ -17,17 +17,20 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
from sqlalchemy import Table, Column, Float, Integer
|
||||
from sqlalchemy.orm import mapper
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.saveddata.price import Price
|
||||
|
||||
|
||||
prices_table = Table("prices", saveddata_meta,
|
||||
Column("typeID", Integer, primary_key=True),
|
||||
Column("price", Float, default=0.0),
|
||||
Column("time", Integer, nullable=False),
|
||||
Column("failed", Integer))
|
||||
Column("status", Integer, nullable=False))
|
||||
|
||||
|
||||
mapper(Price, prices_table, properties={
|
||||
"_Price__price": prices_table.c.price,
|
||||
|
||||
@@ -542,8 +542,17 @@ def commit():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.commit()
|
||||
saveddata_session.flush()
|
||||
except Exception as ex:
|
||||
except Exception:
|
||||
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()
|
||||
exc_info = sys.exc_info()
|
||||
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||
|
||||
@@ -141,6 +141,23 @@ class HandledModuleList(HandledList):
|
||||
self.remove(mod)
|
||||
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):
|
||||
mod.position = index
|
||||
i = index
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceCapNeed
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (493 of 947)
|
||||
# Items from category: Charge (493 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceRange
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (587 of 947)
|
||||
# Items from category: Charge (587 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# ammoSpeedMultiplier
|
||||
#
|
||||
# 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: Structure Festival Charges (3 of 3)
|
||||
# Special Edition Assetss from group: Festival Charges Expired (2 of 2)
|
||||
# Items from market group: Special Edition Assets > Special Edition Festival Assets (30 of 33)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoTrackingMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (182 of 947)
|
||||
# Items from category: Charge (182 of 949)
|
||||
# Charges from group: Projectile Ammo (128 of 128)
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -9,4 +9,7 @@ type = "active"
|
||||
def handler(fit, module, context):
|
||||
amount = module.getModifiedItemAttr("armorDamageAmount")
|
||||
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: grade Edge (10 of 12)
|
||||
# Skill: Neurotoxin Control
|
||||
runTime = 'early'
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterShieldCapacityPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (12 of 69)
|
||||
# Implants from group: Booster (12 of 70)
|
||||
type = "boosterSideEffect"
|
||||
|
||||
# User-friendly name for the side effect
|
||||
|
||||
@@ -6,5 +6,5 @@ type = "passive"
|
||||
|
||||
|
||||
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"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cynosuralGeneration
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Cynosural Field (2 of 2)
|
||||
# Modules from group: Cynosural Field Generator (2 of 2)
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ type = "passive"
|
||||
|
||||
def handler(fit, container, context):
|
||||
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",
|
||||
container.getModifiedItemAttr("consumptionQuantityBonusPercentage") * level)
|
||||
|
||||
@@ -11,6 +11,5 @@ def handler(fit, module, context):
|
||||
bonus = "%s%sDamageResonance" % (attrPrefix, damageType)
|
||||
bonus = "%s%s" % (bonus[0].lower(), bonus[1:])
|
||||
booster = "%s%sDamageResonance" % (layer, damageType)
|
||||
penalize = False if layer == 'hull' else True
|
||||
fit.ship.multiplyItemAttr(bonus, module.getModifiedItemAttr(booster),
|
||||
stackingPenalties=penalize, penaltyGroup="preMul")
|
||||
stackingPenalties=True, penaltyGroup="preMul")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# doHacking
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Data Miners (9 of 9)
|
||||
# Modules from group: Data Miners (10 of 10)
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneArmorDamageBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneHullRepairBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneShieldBonusBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -8,4 +8,6 @@ runtime = "late"
|
||||
|
||||
def handler(fit, src, context):
|
||||
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
|
||||
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:
|
||||
# Variations of module: Ice Harvester Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
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):
|
||||
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"))
|
||||
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"), stackingPenalties=True)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
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:
|
||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||
# Module: ML-EKP 'Polybolos' Ballistic Control System
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileDMGBonus
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ballistic Control system (21 of 21)
|
||||
# Modules from group: Ballistic Control system (22 of 22)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileLauncherSpeedMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ballistic Control system (21 of 21)
|
||||
# Modules from group: Ballistic Control system (22 of 22)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -9,4 +9,7 @@ def handler(fit, container, context):
|
||||
if "projected" in context:
|
||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||
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:
|
||||
# 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
|
||||
type = "overheat"
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# overloadSelfDurationBonus
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||
# Modules from group: Capacitor Booster (59 of 59)
|
||||
# Modules from group: Energy Neutralizer (54 of 54)
|
||||
# Modules from group: Energy Nosferatu (54 of 54)
|
||||
# Modules from group: Hull Repair Unit (25 of 25)
|
||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
||||
# Modules from group: Remote Shield Booster (38 of 38)
|
||||
# Modules from group: Smart Bomb (118 of 118)
|
||||
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||
# Modules named like: Ancillary Remote (8 of 8)
|
||||
# Modules named like: Remote Repairer (56 of 56)
|
||||
# Module: Reactive Armor Hardener
|
||||
# Module: Target Spectrum Breaker
|
||||
type = "overheat"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# remoteCapacitorTransmitterPowerNeedBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
|
||||
# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shieldTransportCpuNeedBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# shipBonusSmartbombCapNeedRoleBonus2
|
||||
#
|
||||
# Used by:
|
||||
# Variations of ship: Rodiva (2 of 2)
|
||||
# Ship: Damavik
|
||||
# Ship: Drekavac
|
||||
# Ship: Hydra
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ancillary Remote Armor Repairer (4 of 4)
|
||||
runTime = "late"
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, module, context, **kwargs):
|
||||
@@ -17,4 +18,7 @@ def handler(fit, module, context, **kwargs):
|
||||
|
||||
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
||||
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:
|
||||
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||
runTime = "late"
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
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:
|
||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, container, context, **kwargs):
|
||||
if "projected" in context:
|
||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||
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:
|
||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
|
||||
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, src, context, **kwargs):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ type = "passive"
|
||||
|
||||
def handler(fit, skill, context):
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (4 of 4)
|
||||
# Skill: Drone Durability
|
||||
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
|
||||
#
|
||||
# Used by:
|
||||
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
|
||||
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
|
||||
# Skill: Drone Interfacing
|
||||
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.__assistive = None
|
||||
self.__overrides = None
|
||||
self.__price = None
|
||||
self.__priceObj = None
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
@@ -446,34 +446,33 @@ class Item(EqBase):
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
|
||||
# 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))
|
||||
self.__price = None
|
||||
self.__priceObj = None
|
||||
|
||||
if self.__price is None:
|
||||
if self.__priceObj is None:
|
||||
db_price = eos.db.getPrice(self.ID)
|
||||
# do not yet have a price in the database for this item, create one
|
||||
if db_price is None:
|
||||
pyfalog.debug("Creating a price for {}".format(self.ID))
|
||||
self.__price = types_Price(self.ID)
|
||||
eos.db.add(self.__price)
|
||||
eos.db.commit()
|
||||
self.__priceObj = types_Price(self.ID)
|
||||
eos.db.add(self.__priceObj)
|
||||
eos.db.flush()
|
||||
else:
|
||||
self.__price = db_price
|
||||
self.__priceObj = db_price
|
||||
|
||||
return self.__price
|
||||
return self.__priceObj
|
||||
|
||||
@property
|
||||
def isAbyssal(self):
|
||||
if Item.ABYSSAL_TYPES is None:
|
||||
Item.getAbyssalYypes()
|
||||
Item.getAbyssalTypes()
|
||||
|
||||
return self.ID in Item.ABYSSAL_TYPES
|
||||
|
||||
@classmethod
|
||||
def getAbyssalYypes(cls):
|
||||
def getAbyssalTypes(cls):
|
||||
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
||||
|
||||
@property
|
||||
|
||||
@@ -75,7 +75,7 @@ class FitDpsGraph(Graph):
|
||||
pyfalog.critical(e)
|
||||
|
||||
for mod in fit.modules:
|
||||
dps, _ = mod.damageStats(fit.targetResists)
|
||||
dps = mod.getDps(targetResists=fit.targetResists).total
|
||||
if mod.hardpoint == Hardpoint.TURRET:
|
||||
if mod.state >= State.ACTIVE:
|
||||
total += dps * self.calculateTurretMultiplier(mod, data)
|
||||
@@ -88,7 +88,7 @@ class FitDpsGraph(Graph):
|
||||
for drone in fit.drones:
|
||||
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
|
||||
drone, data)
|
||||
dps, _ = drone.damageStats(fit.targetResists)
|
||||
dps = drone.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
# this is janky as fuck
|
||||
@@ -98,7 +98,7 @@ class FitDpsGraph(Graph):
|
||||
for ability in fighter.abilities:
|
||||
if ability.dealsDamage and ability.active:
|
||||
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
||||
dps, _ = ability.damageStats(fit.targetResists)
|
||||
dps = ability.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
return total
|
||||
|
||||
@@ -215,6 +215,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if force is not None:
|
||||
if cappingValue is not None:
|
||||
force = min(force, cappingValue)
|
||||
if key in (50, 30, 48, 11):
|
||||
force = round(force, 2)
|
||||
return force
|
||||
# Grab our values if they're there, otherwise we'll take default values
|
||||
preIncrease = self.__preIncreases.get(key, 0)
|
||||
@@ -268,7 +270,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
# Cap value if we have cap defined
|
||||
if cappingValue is not None:
|
||||
val = min(val, cappingValue)
|
||||
|
||||
if key in (50, 30, 48, 11):
|
||||
val = round(val, 2)
|
||||
return val
|
||||
|
||||
def __handleSkill(self, skillName):
|
||||
|
||||
@@ -42,13 +42,18 @@ class DamagePattern(object):
|
||||
return ehp
|
||||
|
||||
def calculateEffectiveTank(self, fit, tankInfo):
|
||||
ehps = {}
|
||||
passiveShield = fit.calculateShieldRecharge()
|
||||
ehps["passiveShield"] = self.effectivify(fit, passiveShield, "shield")
|
||||
for type in ("shield", "armor", "hull"):
|
||||
ehps["%sRepair" % type] = self.effectivify(fit, tankInfo["%sRepair" % type], type)
|
||||
|
||||
return ehps
|
||||
typeMap = {
|
||||
"passiveShield": "shield",
|
||||
"shieldRepair": "shield",
|
||||
"armorRepair": "armor",
|
||||
"armorRepairPreSpool": "armor",
|
||||
"armorRepairFullSpool": "armor",
|
||||
"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):
|
||||
type = type if type != "hull" else ""
|
||||
|
||||
@@ -24,12 +24,13 @@ from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
|
||||
def __init__(self, item):
|
||||
@@ -65,8 +66,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
@@ -120,37 +121,67 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats()
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or self.amountActive <= 0:
|
||||
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):
|
||||
self.itemID = typeID
|
||||
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
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -208,8 +239,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -26,6 +26,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.saveddata.module import Slot
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -87,8 +88,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
@@ -172,43 +172,88 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats()
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
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):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.active and self.amountActive > 0:
|
||||
for ability in self.abilities:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
self.__dps += dps
|
||||
self.__volley += volley
|
||||
|
||||
# For forward compatability this assumes a fighter
|
||||
# can have more than 2 damaging abilities and/or
|
||||
# multiple that use charges.
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
constantDps = 0
|
||||
for ability in self.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.numShots == 0:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
constantDps += dps
|
||||
continue
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
self.__dps = max(constantDps, self.__dps * shortestActive / (shortestActive + longestReload))
|
||||
|
||||
return self.__dps, self.__volley
|
||||
def getDps(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Analyze cooldowns when reload is factored in
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
peakEm = 0
|
||||
peakTherm = 0
|
||||
peakKin = 0
|
||||
peakExp = 0
|
||||
steadyEm = 0
|
||||
steadyTherm = 0
|
||||
steadyKin = 0
|
||||
steadyExp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
# Peak dps
|
||||
peakEm += abilityDps.em
|
||||
peakTherm += abilityDps.thermal
|
||||
peakKin += abilityDps.kinetic
|
||||
peakExp += abilityDps.explosive
|
||||
# Infinite use - add to steady dps
|
||||
if ability.numShots == 0:
|
||||
steadyEm += abilityDps.em
|
||||
steadyTherm += abilityDps.thermal
|
||||
steadyKin += abilityDps.kinetic
|
||||
steadyExp += abilityDps.explosive
|
||||
else:
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
steadyDps = DmgTypes(steadyEm, steadyTherm, steadyKin, steadyExp)
|
||||
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
|
||||
def maxRange(self):
|
||||
@@ -251,8 +296,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -21,12 +21,12 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
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
|
||||
# with the fighter squadron role
|
||||
@@ -118,30 +118,38 @@ class FighterAbility(object):
|
||||
|
||||
return speed
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage and self.active:
|
||||
cycleTime = self.cycleTime
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or not self.active:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
em = self.fighter.getModifiedChargeAttr("emDamage", 0)
|
||||
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":
|
||||
# bomb calcs
|
||||
volley = sum([(self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * (
|
||||
1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
||||
else:
|
||||
volley = sum(map(lambda d2, d:
|
||||
(self.fighter.getModifiedItemAttr(
|
||||
"{}Damage{}".format(self.attrPrefix, d2)) or 0) *
|
||||
(1 - getattr(targetResists, "{}Amount".format(d), 0)),
|
||||
self.DAMAGE_TYPES2, self.DAMAGE_TYPES))
|
||||
|
||||
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 getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
dpsFactor = 1 / (self.cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
|
||||
@@ -34,6 +34,7 @@ from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.module import Module, State, Slot, Hardpoint
|
||||
from eos.utils.stats import DmgTypes
|
||||
from logbook import Logger
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -120,10 +121,11 @@ class Fit(object):
|
||||
def build(self):
|
||||
self.__extraDrains = []
|
||||
self.__ehp = None
|
||||
self.__weaponDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__remoteRepMap = {}
|
||||
self.__minerYield = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__sustainableTank = None
|
||||
@@ -135,12 +137,6 @@ class Fit(object):
|
||||
self.__capUsed = None
|
||||
self.__capRecharge = None
|
||||
self.__calculatedTargets = []
|
||||
self.__remoteReps = {
|
||||
"Armor" : None,
|
||||
"Shield" : None,
|
||||
"Hull" : None,
|
||||
"Capacitor": None,
|
||||
}
|
||||
self.factorReload = False
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
@@ -154,9 +150,9 @@ class Fit(object):
|
||||
@targetResists.setter
|
||||
def targetResists(self, targetResists):
|
||||
self.__targetResists = targetResists
|
||||
self.__weaponDPS = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
|
||||
@property
|
||||
@@ -277,41 +273,31 @@ class Fit(object):
|
||||
def projectedFighters(self):
|
||||
return self.__projectedFighters
|
||||
|
||||
@property
|
||||
def weaponDPS(self):
|
||||
if self.__weaponDPS is None:
|
||||
self.calculateWeaponStats()
|
||||
def getWeaponDps(self, spoolOptions=None):
|
||||
if spoolOptions not in self.__weaponDpsMap:
|
||||
self.calculateWeaponDmgStats(spoolOptions)
|
||||
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 weaponVolley(self):
|
||||
if self.__weaponVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
def getDroneDps(self):
|
||||
if self.__droneDps is None:
|
||||
self.calculateDroneDmgStats()
|
||||
return self.__droneDps
|
||||
|
||||
return self.__weaponVolley
|
||||
|
||||
@property
|
||||
def droneDPS(self):
|
||||
if self.__droneDPS is None:
|
||||
self.calculateWeaponStats()
|
||||
|
||||
return self.__droneDPS
|
||||
|
||||
@property
|
||||
def droneVolley(self):
|
||||
def getDroneVolley(self):
|
||||
if self.__droneVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
|
||||
self.calculateDroneDmgStats()
|
||||
return self.__droneVolley
|
||||
|
||||
@property
|
||||
def totalDPS(self):
|
||||
return self.droneDPS + self.weaponDPS
|
||||
def getTotalDps(self, spoolOptions=None):
|
||||
return self.getDroneDps() + self.getWeaponDps(spoolOptions=spoolOptions)
|
||||
|
||||
@property
|
||||
def totalVolley(self):
|
||||
return self.droneVolley + self.weaponVolley
|
||||
def getTotalVolley(self, spoolOptions=None):
|
||||
return self.getDroneVolley() + self.getWeaponVolley(spoolOptions=spoolOptions)
|
||||
|
||||
@property
|
||||
def minerYield(self):
|
||||
@@ -409,12 +395,13 @@ class Fit(object):
|
||||
|
||||
def clear(self, projected=False, command=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__remoteRepMap = {}
|
||||
self.__minerYield = None
|
||||
self.__weaponVolley = None
|
||||
self.__effectiveSustainableTank = None
|
||||
self.__sustainableTank = None
|
||||
self.__droneDPS = None
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__ehp = None
|
||||
@@ -426,9 +413,6 @@ class Fit(object):
|
||||
self.ecmProjectedStr = 1
|
||||
# self.commandBonuses = {}
|
||||
|
||||
for remoterep_type in self.__remoteReps:
|
||||
self.__remoteReps[remoterep_type] = None
|
||||
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
@@ -1032,11 +1016,11 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
def pgUsed(self):
|
||||
return self.getItemAttrOnlineSum(self.modules, "power")
|
||||
return round(self.getItemAttrOnlineSum(self.modules, "power"), 2)
|
||||
|
||||
@property
|
||||
def cpuUsed(self):
|
||||
return self.getItemAttrOnlineSum(self.modules, "cpu")
|
||||
return round(self.getItemAttrOnlineSum(self.modules, "cpu"), 2)
|
||||
|
||||
@property
|
||||
def droneBandwidthUsed(self):
|
||||
@@ -1151,149 +1135,6 @@ class Fit(object):
|
||||
|
||||
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):
|
||||
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
|
||||
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
|
||||
@@ -1377,92 +1218,27 @@ class Fit(object):
|
||||
self.__capStable = True
|
||||
self.__capState = 100
|
||||
|
||||
@property
|
||||
def remoteReps(self):
|
||||
force_recalc = False
|
||||
for remote_type in self.__remoteReps:
|
||||
if self.__remoteReps[remote_type] is None:
|
||||
force_recalc = True
|
||||
break
|
||||
def getRemoteReps(self, spoolOptions=None):
|
||||
if spoolOptions not in self.__remoteRepMap:
|
||||
remoteReps = {}
|
||||
|
||||
if force_recalc is False:
|
||||
return self.__remoteReps
|
||||
for module in self.modules:
|
||||
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
|
||||
# all values to 0.
|
||||
for remote_type in self.__remoteReps:
|
||||
self.__remoteReps[remote_type] = 0
|
||||
for drone in self.drones:
|
||||
rrType, rrAmount = drone.getRemoteReps()
|
||||
if rrType:
|
||||
if rrType not in remoteReps:
|
||||
remoteReps[rrType] = 0
|
||||
remoteReps[rrType] += rrAmount
|
||||
|
||||
for stuff in chain(self.modules, self.drones):
|
||||
if stuff.item:
|
||||
if stuff.item.ID == 10250:
|
||||
pass
|
||||
remote_type = None
|
||||
self.__remoteRepMap[spoolOptions] = remoteReps
|
||||
|
||||
# Only apply the charged multiplier if we have a charge in our ancil reppers (#1135)
|
||||
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
|
||||
return self.__remoteRepMap[spoolOptions]
|
||||
|
||||
@property
|
||||
def hp(self):
|
||||
@@ -1485,11 +1261,14 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
def tank(self):
|
||||
hps = {"passiveShield": self.calculateShieldRecharge()}
|
||||
for type in ("shield", "armor", "hull"):
|
||||
hps["%sRepair" % type] = self.extraAttributes["%sRepair" % type]
|
||||
|
||||
return hps
|
||||
reps = {
|
||||
"passiveShield": self.calculateShieldRecharge(),
|
||||
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||
"armorRepair": self.extraAttributes["armorRepair"],
|
||||
"armorRepairPreSpool": self.extraAttributes["armorRepairPreSpool"],
|
||||
"armorRepairFullSpool": self.extraAttributes["armorRepairFullSpool"],
|
||||
"hullRepair": self.extraAttributes["hullRepair"]}
|
||||
return reps
|
||||
|
||||
@property
|
||||
def effectiveTank(self):
|
||||
@@ -1497,24 +1276,153 @@ class Fit(object):
|
||||
if self.damagePattern is None:
|
||||
ehps = self.tank
|
||||
else:
|
||||
ehps = self.damagePattern.calculateEffectiveTank(self, self.extraAttributes)
|
||||
ehps = self.damagePattern.calculateEffectiveTank(self, self.tank)
|
||||
|
||||
self.__effectiveTank = ehps
|
||||
|
||||
return self.__effectiveTank
|
||||
|
||||
@property
|
||||
def sustainableTank(self):
|
||||
if self.__sustainableTank is None:
|
||||
self.calculateSustainableTank()
|
||||
|
||||
return self.__sustainableTank
|
||||
|
||||
@property
|
||||
def effectiveSustainableTank(self):
|
||||
if self.__effectiveSustainableTank is None:
|
||||
if self.damagePattern is None:
|
||||
eshps = self.sustainableTank
|
||||
tank = self.sustainableTank
|
||||
else:
|
||||
eshps = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
||||
|
||||
self.__effectiveSustainableTank = eshps
|
||||
|
||||
tank = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
||||
self.__effectiveSustainableTank = tank
|
||||
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):
|
||||
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
||||
if scanRes is not None and scanRes > 0:
|
||||
@@ -1537,30 +1445,30 @@ class Fit(object):
|
||||
self.__minerYield = minerYield
|
||||
self.__droneYield = droneYield
|
||||
|
||||
def calculateWeaponStats(self):
|
||||
weaponDPS = 0
|
||||
droneDPS = 0
|
||||
weaponVolley = 0
|
||||
droneVolley = 0
|
||||
def calculateWeaponDmgStats(self, spoolOptions):
|
||||
weaponVolley = DmgTypes(0, 0, 0, 0)
|
||||
weaponDps = DmgTypes(0, 0, 0, 0)
|
||||
|
||||
for mod in self.modules:
|
||||
dps, volley = mod.damageStats(self.targetResists)
|
||||
weaponDPS += dps
|
||||
weaponVolley += volley
|
||||
weaponVolley += mod.getVolley(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||
weaponDps += mod.getDps(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||
|
||||
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:
|
||||
dps, volley = drone.damageStats(self.targetResists)
|
||||
droneDPS += dps
|
||||
droneVolley += volley
|
||||
droneVolley += drone.getVolley(targetResists=self.targetResists)
|
||||
droneDps += drone.getDps(targetResists=self.targetResists)
|
||||
|
||||
for fighter in self.fighters:
|
||||
dps, volley = fighter.damageStats(self.targetResists)
|
||||
droneDPS += dps
|
||||
droneVolley += volley
|
||||
droneVolley += fighter.getVolley(targetResists=self.targetResists)
|
||||
droneDps += fighter.getDps(targetResists=self.targetResists)
|
||||
|
||||
self.__weaponDPS = weaponDPS
|
||||
self.__weaponVolley = weaponVolley
|
||||
self.__droneDPS = droneDPS
|
||||
self.__droneDps = droneDps
|
||||
self.__droneVolley = droneVolley
|
||||
|
||||
@property
|
||||
|
||||
@@ -28,6 +28,9 @@ from eos.enum import Enum
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.citadel import Citadel
|
||||
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__)
|
||||
|
||||
@@ -95,7 +98,6 @@ class Hardpoint(Enum):
|
||||
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
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":
|
||||
self.__charge = None
|
||||
|
||||
self.__dps = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__volley = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
@@ -247,8 +249,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
else:
|
||||
charges = floor(containerCapacity / chargeVolume)
|
||||
return int(charges)
|
||||
charges = int(floatUnerr(containerCapacity / chargeVolume))
|
||||
return charges
|
||||
|
||||
@property
|
||||
def numShots(self):
|
||||
@@ -411,35 +413,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
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
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -459,13 +432,110 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
return self.__miningyield
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats(None)[0]
|
||||
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||
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 volley(self):
|
||||
return self.damageStats(None)[1]
|
||||
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
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
|
||||
def reloadTime(self):
|
||||
@@ -718,9 +788,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__volley = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
|
||||
@@ -18,24 +18,30 @@
|
||||
# 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
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@unique
|
||||
class PriceStatus(IntEnum):
|
||||
notFetched = 0
|
||||
success = 1
|
||||
fail = 2
|
||||
notSupported = 3
|
||||
|
||||
|
||||
class Price(object):
|
||||
def __init__(self, typeID):
|
||||
self.typeID = typeID
|
||||
self.time = 0
|
||||
self.__price = 0
|
||||
self.failed = None
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__item = None
|
||||
self.status = PriceStatus.notFetched
|
||||
|
||||
@property
|
||||
def isValid(self):
|
||||
@@ -43,7 +49,10 @@ class Price(object):
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
return self.__price or 0.0
|
||||
if self.status != PriceStatus.success:
|
||||
return 0
|
||||
else:
|
||||
return self.__price or 0
|
||||
|
||||
@price.setter
|
||||
def price(self, price):
|
||||
|
||||
@@ -29,14 +29,16 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class Ship(ItemAttrShortcut, HandledItem):
|
||||
EXTRA_ATTRIBUTES = {
|
||||
"armorRepair" : 0,
|
||||
"hullRepair" : 0,
|
||||
"shieldRepair" : 0,
|
||||
"maxActiveDrones" : 0,
|
||||
"armorRepair": 0,
|
||||
"armorRepairPreSpool": 0,
|
||||
"armorRepairFullSpool": 0,
|
||||
"hullRepair": 0,
|
||||
"shieldRepair": 0,
|
||||
"maxActiveDrones": 0,
|
||||
"maxTargetsLockedFromSkills": 2,
|
||||
"droneControlRange" : 20000,
|
||||
"cloaked" : False,
|
||||
"siege" : False
|
||||
"droneControlRange": 20000,
|
||||
"cloaked": False,
|
||||
"siege": False
|
||||
# We also have speedLimit for Entosis Link, but there seems to be an
|
||||
# issue with naming it exactly "speedLimit" due to unknown reasons.
|
||||
# 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
|
||||
|
||||
versionString = "{0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)
|
||||
versionString = "{0}".format(config.version)
|
||||
licenses = (
|
||||
"pyfa is released under GNU GPLv3 - see included LICENSE file",
|
||||
"All EVE-Online related materials are property of CCP hf.",
|
||||
|
||||
@@ -4,6 +4,7 @@ import wx
|
||||
from service.fit import Fit
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||
|
||||
|
||||
class NotesView(wx.Panel):
|
||||
@@ -17,9 +18,16 @@ class NotesView(wx.Panel):
|
||||
self.SetSizer(mainSizer)
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.Bind(wx.EVT_TEXT, self.onText)
|
||||
self.editNotes.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
||||
self.saveTimer = wx.Timer(self)
|
||||
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):
|
||||
sFit = Fit.getInstance()
|
||||
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()
|
||||
item = getattr(selection[0], "item", selection[0])
|
||||
isMutated = getattr(selection[0], "isMutated", False)
|
||||
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
|
||||
if mktGrp is None or mktGrp.ID == 1663:
|
||||
@@ -43,7 +46,10 @@ class MarketJump(ContextMenu):
|
||||
if srcContext in ("fittingCharge", "projectedCharge"):
|
||||
item = selection[0].charge
|
||||
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:
|
||||
item = selection[0]
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import math
|
||||
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
|
||||
from gui.attribute_gauge import AttributeGauge
|
||||
from eos.utils.float import floatUnerr
|
||||
|
||||
_ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
|
||||
|
||||
@@ -58,22 +61,41 @@ class AttributeSlider(wx.Panel):
|
||||
|
||||
self.inverse = inverse
|
||||
|
||||
# The internal slider basically represents the percentage towards the end of the range. It has to be normalized
|
||||
# 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
|
||||
# toward one end
|
||||
def getStep(valRange):
|
||||
"""
|
||||
Find step for the passed range, which is based on 1, 2 or 5.
|
||||
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
|
||||
# (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)
|
||||
#
|
||||
# self.SliderMinValue = -100
|
||||
# self.SliderMaxValue = 100
|
||||
# self.SliderValue = 0
|
||||
def getDigitPlaces(minValue, maxValue):
|
||||
minDigits = 3
|
||||
maxDigits = 5
|
||||
currentDecision = minDigits
|
||||
for value in (floatUnerr(minValue), floatUnerr(maxValue)):
|
||||
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)
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ from service.attribute import Attribute
|
||||
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):
|
||||
def __init__(self, parent, stuff, item, items, context=None):
|
||||
# Start dealing with Price stuff to get that thread going
|
||||
@@ -27,8 +31,7 @@ class ItemCompare(wx.Panel):
|
||||
self.currentSort = None
|
||||
self.sortReverse = False
|
||||
self.item = item
|
||||
self.items = sorted(items,
|
||||
key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0)
|
||||
self.items = sorted(items, key=defaultSort)
|
||||
self.attrs = {}
|
||||
|
||||
# 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.
|
||||
attr = str(list(self.attrs.keys())[sort - 1])
|
||||
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:
|
||||
# Clicked on a column that's not part of our array (price most likely)
|
||||
self.sortReverse = False
|
||||
func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0
|
||||
# Price
|
||||
if sort == len(self.attrs) + 1:
|
||||
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)
|
||||
|
||||
@@ -160,7 +168,7 @@ class ItemCompare(wx.Panel):
|
||||
self.paramList.SetItem(i, x + 1, valueUnit)
|
||||
|
||||
# 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.Layout()
|
||||
|
||||
@@ -15,7 +15,6 @@ class ItemDescription(wx.Panel):
|
||||
fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
|
||||
|
||||
self.description = wx.html.HtmlWindow(self)
|
||||
|
||||
if not item.description:
|
||||
return
|
||||
|
||||
@@ -24,8 +23,11 @@ class ItemDescription(wx.Panel):
|
||||
desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P<inside>.*?)<( *)/( *)font( *)>", "\g<inside>", desc)
|
||||
# Strip URLs
|
||||
desc = re.sub("<( *)a(.*?)>(?P<inside>.*?)<( *)/( *)a( *)>", "\g<inside>", desc)
|
||||
desc = "<body bgcolor='" + bgcolor.GetAsString(wx.C2S_HTML_SYNTAX) + "' text='" + fgcolor.GetAsString(
|
||||
wx.C2S_HTML_SYNTAX) + "' >" + desc + "</body>"
|
||||
desc = "<body style='background-color: {}; color: {}'>{}</body>".format(
|
||||
bgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
|
||||
fgcolor.GetAsString(wx.C2S_CSS_SYNTAX),
|
||||
desc
|
||||
)
|
||||
|
||||
self.description.SetPage(desc)
|
||||
|
||||
|
||||
@@ -22,38 +22,67 @@ class ItemMutator(wx.Panel):
|
||||
self.item = item
|
||||
self.timer = None
|
||||
self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||
|
||||
font = parent.GetFont()
|
||||
font.SetWeight(wx.BOLD)
|
||||
|
||||
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.badColor = wx.Colour(255, 64, 0)
|
||||
|
||||
self.event_mapping = {}
|
||||
|
||||
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
|
||||
# create array for the two ranges
|
||||
min_t = [m.minValue, m.minMod, None]
|
||||
max_t = [m.maxValue, m.maxMod, None]
|
||||
# Format: [raw value, modifier applied to base raw value, display value]
|
||||
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue))
|
||||
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
|
||||
min_t[2] = min_t[1] < 1 if not m.highIsGood else 1 < min_t[1]
|
||||
max_t[2] = max_t[1] < 1 if not m.highIsGood else 1 < max_t[1]
|
||||
|
||||
# Lastly, we need to determine which range value is "worse" (left side) or "better" (right side)
|
||||
if (m.highIsGood and min_t[1] > max_t[1]) or (not m.highIsGood and min_t[1] < max_t[1]):
|
||||
better_range = min_t
|
||||
# minValue/maxValue do not always correspond to min/max, because these are
|
||||
# just base value multiplied by minMod/maxMod, and in case base is negative
|
||||
# minValue is actually bigger than maxValue
|
||||
if range1[0] <= range2[0]:
|
||||
minRange = range1
|
||||
maxRange = range2
|
||||
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]):
|
||||
worse_range = max_t
|
||||
if (m.highIsGood and minRange[0] >= maxRange[0]) or (not m.highIsGood and minRange[0] <= maxRange[0]):
|
||||
betterRange = minRange
|
||||
worseRange = maxRange
|
||||
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)
|
||||
|
||||
font = parent.GetFont()
|
||||
font.SetWeight(wx.BOLD)
|
||||
|
||||
headingSizer.Add(BitmapLoader.getStaticBitmap(m.attribute.iconID, self, "icons"), 0, wx.RIGHT, 10)
|
||||
|
||||
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)
|
||||
|
||||
worst_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worse_range[0]), rounding='dec')
|
||||
worst_text = wx.StaticText(self, wx.ID_ANY, worst_val)
|
||||
worst_text.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
|
||||
worseVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worseRange[0]), rounding='dec')
|
||||
worseText = wx.StaticText(self, wx.ID_ANY, worseVal)
|
||||
worseText.SetForegroundColour(self.badColor)
|
||||
|
||||
best_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(better_range[0]), rounding='dec')
|
||||
best_text = wx.StaticText(self, wx.ID_ANY, best_val)
|
||||
best_text.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
|
||||
betterVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(betterRange[0]), rounding='dec')
|
||||
betterText = wx.StaticText(self, wx.ID_ANY, betterVal)
|
||||
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(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)
|
||||
|
||||
slider = AttributeSlider(parent=self,
|
||||
baseValue=m.attribute.unit.SimplifyValue(m.baseValue),
|
||||
minValue=m.attribute.unit.SimplifyValue(min_t[0]),
|
||||
maxValue=m.attribute.unit.SimplifyValue(max_t[0]),
|
||||
inverse=better_range is min_t)
|
||||
baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue),
|
||||
minValue=displayMinRange[1],
|
||||
maxValue=displayMaxRange[1],
|
||||
inverse=displayMaxRange is worseRange)
|
||||
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
|
||||
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
|
||||
self.event_mapping[slider] = m
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import wx
|
||||
import gui.utils.color as colorUtils
|
||||
import gui.utils.draw as drawUtils
|
||||
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||
|
||||
SearchButton, EVT_SEARCH_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_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_ENTER, self.OnTextEnter)
|
||||
|
||||
@@ -83,6 +84,12 @@ class PFSearchBox(wx.Window):
|
||||
self.Clear()
|
||||
event.Skip()
|
||||
|
||||
def OnKeyPress(self, event):
|
||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
def Clear(self):
|
||||
self.EditBox.Clear()
|
||||
# self.EditBox.ChangeValue(self.descriptiveText)
|
||||
|
||||
@@ -81,6 +81,11 @@ class PFContextMenuPref(PreferenceView):
|
||||
rbSizerRow3.Add(self.rbBox7, 1, wx.TOP | wx.RIGHT, 5)
|
||||
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)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
@@ -107,6 +112,9 @@ class PFContextMenuPref(PreferenceView):
|
||||
def OnSetting7Change(self, event):
|
||||
self.settings.set('project', event.GetInt())
|
||||
|
||||
def OnSetting8Change(self, event):
|
||||
self.settings.set('moduleFill', event.GetInt())
|
||||
|
||||
def getImage(self):
|
||||
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)
|
||||
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",
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
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.cbMarketShortcuts.SetValue(self.sFit.serviceFittingOptions["showMarketShortcuts"] or False)
|
||||
self.cbGaugeAnimation.SetValue(self.sFit.serviceFittingOptions["enableGaugeAnimation"])
|
||||
self.cbExportCharges.SetValue(self.sFit.serviceFittingOptions["exportCharges"])
|
||||
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
|
||||
self.chPriceSource.SetStringSelection(self.sFit.serviceFittingOptions["priceSource"])
|
||||
self.chPriceSystem.SetStringSelection(self.sFit.serviceFittingOptions["priceSystem"])
|
||||
@@ -151,7 +146,6 @@ class PFGeneralPref(PreferenceView):
|
||||
self.cbShowTooltip.Bind(wx.EVT_CHECKBOX, self.onCBShowTooltip)
|
||||
self.cbMarketShortcuts.Bind(wx.EVT_CHECKBOX, self.onCBShowShortcuts)
|
||||
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.chPriceSource.Bind(wx.EVT_CHOICE, self.onPricesSourceSelection)
|
||||
self.chPriceSystem.Bind(wx.EVT_CHOICE, self.onPriceSelection)
|
||||
@@ -220,9 +214,6 @@ class PFGeneralPref(PreferenceView):
|
||||
def onCBGaugeAnimation(self, event):
|
||||
self.sFit.serviceFittingOptions["enableGaugeAnimation"] = self.cbGaugeAnimation.GetValue()
|
||||
|
||||
def onCBExportCharges(self, event):
|
||||
self.sFit.serviceFittingOptions["exportCharges"] = self.cbExportCharges.GetValue()
|
||||
|
||||
def onCBOpenFitInNew(self, event):
|
||||
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 gui.bitmap_loader import BitmapLoader
|
||||
from service.fit import Fit
|
||||
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||
|
||||
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_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.SetMinSize(size)
|
||||
@@ -103,9 +104,11 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
def OnBrowserSearchBoxLostFocus(self, event):
|
||||
self.BrowserSearchBox.Show(False)
|
||||
|
||||
def OnBrowserSearchBoxEsc(self, event):
|
||||
def OnBrowserSearchBoxKeyPress(self, event):
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE:
|
||||
self.BrowserSearchBox.Show(False)
|
||||
elif event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
HandleCtrlBackspace(self.BrowserSearchBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ import wx
|
||||
import gui.mainFrame
|
||||
from gui.statsView import StatsView
|
||||
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
|
||||
|
||||
|
||||
@@ -148,27 +149,55 @@ class FirepowerViewFull(StatsView):
|
||||
else:
|
||||
self.stEff.Hide()
|
||||
|
||||
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS", None),
|
||||
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
|
||||
("labelFullVolleyTotal", lambda: fit.totalVolley, 3, 0, 0, "%s", "Volley: %.1f"),
|
||||
("labelFullDpsTotal", lambda: fit.totalDPS, 3, 0, 0, "%s", None))
|
||||
# See GH issue #
|
||||
# if fit is not None and fit.totalYield > 0:
|
||||
# self.miningyield.Show()
|
||||
# else:
|
||||
# self.miningyield.Hide()
|
||||
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest):
|
||||
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||
return ""
|
||||
else:
|
||||
return "Spool up: {}-{}".format(
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# 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
|
||||
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)
|
||||
value = value() if fit is not None else 0
|
||||
value = value if value is not None else 0
|
||||
if self._cachedValues[counter] != value:
|
||||
valueStr = formatAmount(value, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat % valueStr)
|
||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
||||
label.SetToolTip(wx.ToolTip(tipStr))
|
||||
self._cachedValues[counter] = value
|
||||
val = val() if fit is not None else 0
|
||||
preSpoolVal = preSpoolVal() if fit is not None else 0
|
||||
fullSpoolVal = fullSpoolVal() if fit is not None else 0
|
||||
if self._cachedValues[counter] != val:
|
||||
tooltipText = dpsToolTip(preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat.format(
|
||||
formatAmount(val, prec, lowest, highest),
|
||||
"\u02e2" if tooltipText else ""))
|
||||
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||
self._cachedValues[counter] = val
|
||||
counter += 1
|
||||
|
||||
self.panel.Layout()
|
||||
|
||||
@@ -21,7 +21,35 @@
|
||||
import wx
|
||||
from gui.statsView import StatsView
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.utils.numberFormatter import formatAmount, roundToPrec
|
||||
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):
|
||||
@@ -48,56 +76,46 @@ class OutgoingViewFull(StatsView):
|
||||
|
||||
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
||||
|
||||
counter = 0
|
||||
|
||||
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:
|
||||
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||
baseBox = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 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))
|
||||
|
||||
setattr(self, "label%s" % outgoingType, lbl)
|
||||
setattr(self, labelName, lbl)
|
||||
|
||||
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
||||
self._cachedValues.append(0)
|
||||
counter += 1
|
||||
|
||||
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
||||
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
stats = [
|
||||
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
|
||||
]
|
||||
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
|
||||
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||
return False, text
|
||||
else:
|
||||
return True, "{}\nSpool up: {}-{}".format(
|
||||
text,
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
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)
|
||||
value = value() if fit is not None else 0
|
||||
value = value if value is not None else 0
|
||||
if self._cachedValues[counter] != value:
|
||||
valueStr = formatAmount(value, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat % valueStr)
|
||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
||||
label.SetToolTip(wx.ToolTip(tipStr))
|
||||
self._cachedValues[counter] = value
|
||||
val = val(fit, defaultSpoolValue) if fit is not None else 0
|
||||
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
|
||||
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
|
||||
if self._cachedValues[counter] != val:
|
||||
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat.format(
|
||||
formatAmount(val, prec, lowest, highest),
|
||||
"\u02e2" if hasSpool else ""))
|
||||
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||
self._cachedValues[counter] = val
|
||||
counter += 1
|
||||
self.panel.Layout()
|
||||
self.headerPanel.Layout()
|
||||
|
||||
@@ -20,7 +20,35 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
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):
|
||||
@@ -47,56 +75,46 @@ class OutgoingViewMinimal(StatsView):
|
||||
|
||||
contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0)
|
||||
|
||||
counter = 0
|
||||
|
||||
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:
|
||||
for labelName, labelDesc, valueFormat, image, tooltip, val, preSpoolVal, fullSpoolVal, prec, lowest, highest in stats:
|
||||
baseBox = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 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")
|
||||
baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, labelDesc), 0, wx.ALIGN_CENTER)
|
||||
|
||||
lbl = wx.StaticText(parent, wx.ID_ANY, valueFormat.format(0, ""))
|
||||
lbl.SetToolTip(wx.ToolTip(tooltip))
|
||||
|
||||
setattr(self, "label%s" % outgoingType, lbl)
|
||||
setattr(self, labelName, lbl)
|
||||
|
||||
baseBox.Add(lbl, 0, wx.ALIGN_CENTER)
|
||||
self._cachedValues.append(0)
|
||||
counter += 1
|
||||
|
||||
sizerOutgoing.Add(baseBox, 1, wx.ALIGN_LEFT)
|
||||
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
stats = [
|
||||
("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None),
|
||||
("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None),
|
||||
]
|
||||
def formatTooltip(text, preSpool, fullSpool, prec, lowest, highest):
|
||||
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
|
||||
return False, text
|
||||
else:
|
||||
return True, "{}\nSpool up: {}-{}".format(
|
||||
text,
|
||||
formatAmount(preSpool, prec, lowest, highest),
|
||||
formatAmount(fullSpool, prec, lowest, highest))
|
||||
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
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)
|
||||
value = value() if fit is not None else 0
|
||||
value = value if value is not None else 0
|
||||
if self._cachedValues[counter] != value:
|
||||
valueStr = formatAmount(value, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat % valueStr)
|
||||
tipStr = valueFormat % valueStr if altFormat is None else altFormat % value
|
||||
label.SetToolTip(wx.ToolTip(tipStr))
|
||||
self._cachedValues[counter] = value
|
||||
val = val(fit, defaultSpoolValue) if fit is not None else 0
|
||||
preSpoolVal = preSpoolVal(fit) if fit is not None else 0
|
||||
fullSpoolVal = fullSpoolVal(fit) if fit is not None else 0
|
||||
if self._cachedValues[counter] != val:
|
||||
hasSpool, tooltipText = formatTooltip(tooltip, preSpoolVal, fullSpoolVal, prec, lowest, highest)
|
||||
label.SetLabel(valueFormat.format(
|
||||
formatAmount(val, prec, lowest, highest),
|
||||
"\u02e2" if hasSpool else ""))
|
||||
label.SetToolTip(wx.ToolTip(tooltipText))
|
||||
self._cachedValues[counter] = val
|
||||
counter += 1
|
||||
self.panel.Layout()
|
||||
self.headerPanel.Layout()
|
||||
|
||||
@@ -63,15 +63,20 @@ class RechargeViewFull(StatsView):
|
||||
|
||||
# Add an empty label first for correct alignment.
|
||||
sizerTankStats.Add(wx.StaticText(contentPanel, wx.ID_ANY, ""), 0)
|
||||
toolTipText = {"shieldPassive": "Passive shield recharge", "shieldActive": "Active shield boost",
|
||||
"armorActive": "Armor repair amount", "hullActive": "Hull repair amount"}
|
||||
toolTipText = {
|
||||
"shieldPassive": "Passive shield recharge",
|
||||
"shieldActive": "Active shield boost",
|
||||
"armorActive": "Armor repair amount",
|
||||
"hullActive": "Hull repair amount"}
|
||||
for tankType in ("shieldPassive", "shieldActive", "armorActive", "hullActive"):
|
||||
bitmap = BitmapLoader.getStaticBitmap("%s_big" % tankType, contentPanel, "gui")
|
||||
tooltip = wx.ToolTip(toolTipText[tankType])
|
||||
bitmap.SetToolTip(tooltip)
|
||||
sizerTankStats.Add(bitmap, 0, wx.ALIGN_CENTER)
|
||||
|
||||
toolTipText = {"reinforced": "Reinforced", "sustained": "Sustained"}
|
||||
toolTipText = {
|
||||
"reinforced": "Reinforced",
|
||||
"sustained": "Sustained"}
|
||||
for stability in ("reinforced", "sustained"):
|
||||
bitmap = BitmapLoader.getStaticBitmap("regen%s_big" % stability.capitalize(), contentPanel, "gui")
|
||||
tooltip = wx.ToolTip(toolTipText[stability])
|
||||
@@ -85,7 +90,6 @@ class RechargeViewFull(StatsView):
|
||||
tankTypeCap = tankType[0].capitalize() + tankType[1:]
|
||||
lbl = wx.StaticText(contentPanel, wx.ID_ANY, "0.0", style=wx.ALIGN_RIGHT)
|
||||
setattr(self, "labelTank%s%s" % (stability.capitalize(), tankTypeCap), lbl)
|
||||
|
||||
box = wx.BoxSizer(wx.HORIZONTAL)
|
||||
box.Add(lbl, 0, wx.EXPAND)
|
||||
|
||||
@@ -115,9 +119,23 @@ class RechargeViewFull(StatsView):
|
||||
unitlbl = getattr(self, "unitLabelTank%s%sActive" % (stability.capitalize(), name.capitalize()))
|
||||
unitlbl.SetLabel(unit)
|
||||
if tank is not None:
|
||||
lbl.SetLabel("%.1f" % tank["%sRepair" % name])
|
||||
amount = tank["{}Repair".format(name)]
|
||||
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:
|
||||
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