Compare commits

..

93 Commits

Author SHA1 Message Date
DarkPhoenix
aa56ab8d6c Bump version 2019-11-21 13:01:14 +03:00
DarkPhoenix
043c430221 Update staticdata to 1610407 2019-11-21 13:00:36 +03:00
DarkPhoenix
dc5cc5855e Show range of bursts projectors, taking ship/citadel radius taken into consideration 2019-11-18 15:18:20 +03:00
DarkPhoenix
59d6266e2b Do not crash on exception classes without message attribute 2019-11-17 21:24:41 +03:00
DarkPhoenix
7495ba67f8 Revert change made for debugging 2019-11-17 17:40:07 +03:00
DarkPhoenix
e649683a4d Accept XML headers with extra info 2019-11-17 17:34:49 +03:00
DarkPhoenix
bec58a5772 Do not crash on cargo fetch failures 2019-11-17 17:29:16 +03:00
DarkPhoenix
e8f9ae8a9c Do not use hires assets on wxGTK 2019-11-16 19:22:37 +03:00
DarkPhoenix
679382e220 Do not separate number and unit in range tooltip, for consistency with other fields 2019-11-15 20:19:23 +03:00
DarkPhoenix
7f86782f54 Change range column tooltip for missiles 2019-11-15 13:23:27 +03:00
DarkPhoenix
f80b7d972f Implement hidden flight time bonus 2019-11-15 12:59:57 +03:00
Anton Vorobyov
c0bd489c1b Merge pull request #2085 from Gochim/master
Possible fix for #2084
2019-11-14 12:38:08 +03:00
Gochim
41e4c2107d Fix for #2084 2019-11-14 11:36:59 +02:00
Gochim
cfc95c272a Possible fix for #2084 2019-11-14 10:35:16 +02:00
DarkPhoenix
f778f9ceae Take ship radius in consideration when displaying missile range in range column 2019-11-13 20:51:14 +03:00
DarkPhoenix
7fb6170bcb Implement missile "falloff" support in graphs 2019-11-13 20:28:52 +03:00
DarkPhoenix
fa37428cd3 Update staticdata to 1573560935 2019-11-12 15:39:57 +03:00
DarkPhoenix
2a2d9d3456 Fix circular import issues for tests 2019-11-12 14:59:43 +03:00
Anton Vorobyov
a91efb681f Merge pull request #2079 from Gochim/minor_fixes
Several minor fixes in different parts of code
2019-11-12 00:07:31 +03:00
Gochim
386e05be8f Fixed method naming in stats.py classes 2019-11-11 19:19:31 +02:00
Anton Vorobyov
65e7bf609d Merge pull request #2078 from Gochim/master
Added central place to get damage types and ehp sources. Added tests
2019-11-11 16:40:22 +03:00
Gochim
7a58d97652 Merge remote-tracking branch 'origin/minor_fixes' into minor_fixes
# Conflicts:
#	eos/utils/stats.py
2019-11-10 17:36:47 +02:00
Gochim
c5118da417 Fixed small issue with #2078 by removing unnecessary prints 2019-11-10 17:35:48 +02:00
Gochim
13b505525d Refactored shipstats.py to use common damage profile and hull type names. Reduced code complexity 2019-11-10 17:27:46 +02:00
Gochim
b682dec363 Second fix for #2076 - use Abstract collections from .abc module. Fixes future issue 2019-11-10 17:27:46 +02:00
Gochim
6aa98e2214 Fixed test_aboutData test 2019-11-10 17:27:46 +02:00
Gochim
8fba988222 Added central place to get damage types and ehp sources. Added tests 2019-11-10 17:27:46 +02:00
Gochim
24a82efe50 Refactored shipstats.py to use common damage profile and hull type names. Reduced code complexity 2019-11-10 17:27:07 +02:00
Gochim
8054fa9267 Second fix for #2076 - use Abstract collections from .abc module. Fixes future issue 2019-11-09 23:27:28 +02:00
Gochim
a0e39a3725 Fixed test_aboutData test 2019-11-09 22:53:52 +02:00
Gochim
185d6d0c51 Merge remote-tracking branch 'origin/master' 2019-11-08 08:35:29 +02:00
Gochim
1975e96848 Added central place to get damage types and ehp sources. Added tests 2019-11-08 08:34:45 +02:00
Gochim
ab37d228ea Added central place to get damage types and ehp sources. Added tests 2019-11-08 08:34:22 +02:00
DarkPhoenix
f48483d754 Merge branch 'master' of github.com:pyfa-org/Pyfa 2019-11-07 18:36:10 +03:00
DarkPhoenix
e6cfd33435 Fix #2076 - use collection ABCs from .abc module 2019-11-07 18:35:14 +03:00
Anton Vorobyov
c29126ce1d Merge pull request #2077 from Gochim/master
Added instructions to run tests for the project
2019-11-07 18:34:58 +03:00
Gochim
c52170b731 Fixed path for bash script call 2019-11-07 14:45:29 +02:00
Gochim
6607dd31bf Merge remote-tracking branch 'origin/master' 2019-11-07 14:27:36 +02:00
Gochim
c6c74be38d Added instructions to run tests for the project 2019-11-07 14:27:14 +02:00
Gochim
41c6062ff9 Added instructions to run tests for the project 2019-11-07 14:26:41 +02:00
DarkPhoenix
9eb3b9e017 Do not use evepraisal info unless there are orders up 2019-11-04 02:50:55 +03:00
DarkPhoenix
6b3e94729c Update staticdata and bump version 2019-10-31 15:05:14 +03:00
DarkPhoenix
fb48f2b5d4 Do not attempt to add entries to name maps if entity fetch failed 2019-10-30 16:46:34 +03:00
DarkPhoenix
cfffc77777 Change format name 2019-10-30 16:13:15 +03:00
DarkPhoenix
f7089f358d Fix stats export parenthesis 2019-10-30 16:12:33 +03:00
DarkPhoenix
06c4f2ce46 Bump version 2019-10-30 16:09:08 +03:00
Anton Vorobyov
83eb0abd92 Merge pull request #2070 from pyfa-org/json_in_repo
Store text staticdata in repo instead of binary
2019-10-30 16:05:55 +03:00
DarkPhoenix
4199b33c47 Merge branch 'master' into json_in_repo 2019-10-30 15:47:47 +03:00
Anton Vorobyov
23cd4bff5a Merge pull request #2069 from Gochim/master
[Updated] Implemented copying the currently open fit stats to the clipboard.
2019-10-30 15:44:53 +03:00
DarkPhoenix
b65f95fe77 Make sure to avoid doing DB updates avoiding sqlalchemy, as we're re-using the same session for pyfa now 2019-10-30 15:39:21 +03:00
DarkPhoenix
32160c94e1 Add extra metadata field which we use during gamedata DB checks 2019-10-30 15:26:26 +03:00
DarkPhoenix
ac02fba98b Move useless category IDs closer to context 2019-10-30 15:18:08 +03:00
DarkPhoenix
cde0108cba Change logging a little so info about DB being rebuilt is always printed to stdout 2019-10-30 15:16:13 +03:00
DarkPhoenix
39dc7e4a46 Compose DB out of data stored externally 2019-10-30 14:56:51 +03:00
Gochim
9943f784a8 Fixed code auto-checks for pull request 2019-10-30 13:34:54 +02:00
Gochim
88ce45f29e Merge remote-tracking branch 'origin/master' 2019-10-30 11:46:25 +02:00
Gochim
7157e876ca Fixed issue with mainFrame after merging 2019-10-30 11:46:03 +02:00
Gochim
0cf88cf7ca Added stats that were more or less agreed on in [Issue #2065] 2019-10-30 11:39:21 +02:00
Gochim
10dfdc3627 Added UI for new type of copying data about fit to the clipboard 2019-10-30 11:39:21 +02:00
Gochim
76bdefcda6 Fixed wording in contributing.md 2019-10-30 11:39:21 +02:00
Gochim
1c2c8cc5f9 Added UI for new type of copying data about fit to the clipboard 2019-10-30 11:39:21 +02:00
Alexander Maryanovsky
58f853de5b Implemented copying the currently open fit stats to the clipboard. 2019-10-30 11:39:21 +02:00
Gochim
c052297bf7 Added stats that were more or less agreed on in [Issue #2065] 2019-10-30 09:17:38 +02:00
DarkPhoenix
9e78cd1076 Fix drag-n-dropping module from market into specific empty slot 2019-10-28 13:23:40 +03:00
DarkPhoenix
79f4deacea Show hidden graphs on ctrl-alt-g 2019-10-28 12:56:34 +03:00
Gochim
ff42c4c711 Added UI for new type of copying data about fit to the clipboard 2019-10-26 18:36:38 +03:00
DarkPhoenix
02d31d49d8 Implement graph types to pick best ECM burst + damps ship 2019-10-26 00:30:45 +03:00
DarkPhoenix
64f47fcc24 Do not choke on fits for unknown ships 2019-10-25 01:00:32 +03:00
DarkPhoenix
0ceb8acd64 Rename some fields for the hidden graph 2019-10-24 23:15:38 +03:00
DarkPhoenix
78579e2e13 Adjust ECM burst + damp graph 2019-10-24 23:13:20 +03:00
DarkPhoenix
cf4e1d3935 Consistency fixes 2019-10-24 14:09:08 +03:00
DarkPhoenix
d1be0bb680 Add scanres vs locktime on active fit graph as experimental feature 2019-10-24 13:52:34 +03:00
Gochim
384d9f4614 Fixed wording in contributing.md 2019-10-21 13:22:00 +03:00
Gochim
47434c68f9 Added UI for new type of copying data about fit to the clipboard 2019-10-20 15:25:06 +03:00
Gochim
af88afb6b5 Merge branch 'export-stats' of https://github.com/m-sasha/PyfaAT into m-sasha-export-stats
# Conflicts:
#	gui/mainFrame.py
#	gui/mainMenuBar.py
2019-10-20 14:48:00 +03:00
Anton Vorobyov
536eb1efa5 Merge pull request #2064 from Gochim/master
Merged some additional info from wiki. Improved on readability
2019-10-17 17:19:10 +03:00
Anton Vorobyov
c4c763089e Merge branch 'master' into master 2019-10-17 17:19:00 +03:00
DarkPhoenix
cdfd4c0d8e Invoke pip the same way as on Windows 2019-10-17 17:01:10 +03:00
DarkPhoenix
f9bb8836e5 Upgrade pip when building mac distribution 2019-10-17 16:59:38 +03:00
Gochim
58b2634c8c Merged info from wiki. Improved on readability 2019-10-17 16:50:40 +03:00
Anton Vorobyov
093ae008ce Merge pull request #2063 from Gochim/master
Re-written in much greater detail instructions how to set up the project
2019-10-17 16:05:31 +03:00
Gochim
5f62fc0cdc Merge remote-tracking branch 'origin/master' 2019-10-16 22:08:33 +03:00
Gochim
e7a4b4ac26 Added section "Setting up the project with PyCharm" 2019-10-16 22:07:10 +03:00
Gochim
66e9944cb5 Added "Setting up the project manually" instructions 2019-10-16 22:07:10 +03:00
DIvanchenko
ad8528c248 Plan for CONTRIBUTING.md. Updated link to CONTRIBUTION.md. 2019-10-16 22:06:19 +03:00
DIvanchenko
07d22cd8e4 Created link to CONTRIBUTION.md. Fixed heading 2019-10-16 22:06:19 +03:00
Gochim
2ca50a4658 Added section "Setting up the project with PyCharm" 2019-10-15 23:08:21 +03:00
Gochim
09ff4fd128 Added "Setting up the project manually" instructions 2019-10-15 20:17:48 +03:00
DIvanchenko
d4bdf47d62 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
2019-10-10 18:04:24 +03:00
DIvanchenko
660ee7c4bf Plan for CONTRIBUTING.md. Updated link to CONTRIBUTION.md. 2019-10-10 18:03:28 +03:00
DIvanchenko
1db1f3070b Created link to CONTRIBUTION.md. Fixed heading 2019-10-10 10:50:56 +03:00
DIvanchenko
3dba82c497 Created link to CONTRIBUTION.md. Fixed heading 2019-10-10 10:49:54 +03:00
Alexander Maryanovsky
b2c718d614 Implemented copying the currently open fit stats to the clipboard. 2018-09-08 20:14:34 +03:00
67 changed files with 4030090 additions and 169 deletions

View File

@@ -65,6 +65,8 @@ before_build:
build_script:
- ECHO "Build pyfa:"
# Build gamedata DB
- "python db_update.py"
##########
# PyInstaller - create binaries for pyfa
##########

View File

@@ -13,6 +13,7 @@ matrix:
before_install:
- bash scripts/setup-osx.sh
install:
- python3 db_update.py
- export PYFA_VERSION="$(python3 scripts/dump_version.py)"
- bash scripts/package-osx.sh
before_deploy:

102
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,102 @@
# Contribution
## Requirements
- Python 3.6
- Git CLI installed
- Python, pip and git are all available as command-line commands (add to the path if needed)
Virtual environment will be created in *PyfaEnv* folder. Project will be cloned and run from the *PyfaDEV* folder. Separate virtual environment will be created so required libraries won't clutter the main python installation.
> Commands and screens were created on Windows 10. Please, update all the paths according to your OS.
## Setting up the project manually
Clone the repository
```
git clone <repo> PyfaDEV
```
Create the virtual environment
```
python -m venv PyfaEnv
```
Activate the virtual environment
```
For cmd.exe: PyfaEnv\scripts\activate.bat
For bash: source <venv>/Scripts/activate
```
> For other OS check [Python documentation](https://docs.python.org/3/library/venv.html)
Install requirements for the project from *requirements.txt*
```
pip install -r PyfaDEV\requirements.txt
```
> For some Linux distributions, you may need to install separate wxPython bindings, such as `python-matplotlib-wx`
Check that the libs from *requirements.txt* are installed
```
pip list
```
Test that the project is starting properly
```
python PyfaDEV\pyfa.py
```
## Setting up the project with PyCharm/IntelliJ
Install PyCharm / Other IntelliJ product with Python plugin
After launching - select *Check out from Version Control* -> *GIt*
![welcome](https://user-images.githubusercontent.com/54093496/66862580-d8edab00-ef99-11e9-94e2-e93d7043e620.png)
Login to GitHub, paste the repo URL and select the folder to which to clone the project into, press *Clone*.
![Clone](https://user-images.githubusercontent.com/54093496/66862748-38e45180-ef9a-11e9-9f68-4903baf47385.png)
After process is complete, open *File* -> *Settings* -> *Project* -> *Project Interpreter*.
![Settings](https://user-images.githubusercontent.com/54093496/66862792-544f5c80-ef9a-11e9-9e0f-f64767f3f1b0.png)
Press on options and add new virtual environment.
![venv](https://user-images.githubusercontent.com/54093496/66862833-67622c80-ef9a-11e9-94fa-47cca0158d29.png)
Open project tree view and double-click on the *requirements.txt*. Press *Install requirements*. Install all requirements.
![Reqs](https://user-images.githubusercontent.com/54093496/66862870-7a74fc80-ef9a-11e9-9b18-e64be42c49b8.png)
Create new *Run Configuration*. Set correct *Script path* and *Python interpreter*.
![Run configuraion](https://user-images.githubusercontent.com/54093496/66862970-b4460300-ef9a-11e9-9fb4-20e24759904b.png)
Check that the project is starting properly.
## Running tests
Switch to the proper virtual environment
```
For cmd.exe: PyfaEnv\scripts\activate.bat
For bash: source <venv>/Scripts/activate
```
Install pytest
```
pip install pytest
```
Switch to pyfa directory.
Run tests (any will do)
```
python -m pytest
py.test
```
More information on tests can be found on appropriate [Wiki page](https://github.com/pyfa-org/Pyfa/wiki/Developers:-Writing-Tests-for-Pyfa).

View File

@@ -14,8 +14,7 @@ The latest version along with release notes can always be found on the project's
## Installation
Windows and OS X users are supplied self-contained builds of pyfa on the [latest releases](https://github.com/pyfa-org/Pyfa/releases/latest) page. An `.exe` installer is also available for Windows builds. Linux users can run pyfa using their distribution's Python interpreter. There is no official self-contained package for Linux, however, there are a number of third-party packages available through distribution-specific repositories.
#### OS X
### OS X
Apart from the official release, there is also a [Homebrew](http://brew.sh) option for installing pyfa on OS X. Please note this is maintained by a third-party and is not tested by pyfa developers. Simply fire up in terminal:
```
$ brew install Caskroom/cask/pyfa
@@ -27,11 +26,8 @@ The following is a list of pyfa packages available for certain distributions. Pl
* Arch: https://aur.archlinux.org/packages/pyfa/
* Gentoo: https://github.com/ZeroPointEnergy/gentoo-pyfa-overlay
### Dependencies
If you wish to help with development or simply need to run pyfa through a Python interpreter, the following software is required:
* Python 3.6
* Requirements as listed in `requirements.txt`
## Contribution
If you wish to help with development or you need to run pyfa through a Python interpreter, check out [the instructions](https://github.com/pyfa-org/Pyfa/blob/master/CONTRIBUTING.md).
## Bug Reporting
The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](https://forums.eveonline.com/t/27156) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/pyfa-org/Pyfa/wiki/Bug-Reporting).

View File

@@ -39,6 +39,8 @@ loggingLevel = None
logging_setup = None
cipher = None
clientHash = None
experimentalFeatures = None
version = None
ESI_CACHE = 'esi_cache'
@@ -103,6 +105,7 @@ def defPaths(customSavePath=None):
global cipher
global clientHash
global version
global experimentalFeatures
pyfalog.debug("Configuring Pyfa")
@@ -168,6 +171,10 @@ def defPaths(customSavePath=None):
logPath = os.path.join(savePath, logFile)
experimentalFeatures = getattr(configforced, "experimentalFeatures", experimentalFeatures)
if experimentalFeatures is None:
experimentalFeatures = False
# DON'T MODIFY ANYTHING BELOW
import eos.config

107
scripts/jsonToSql.py → db_update.py Executable file → Normal file
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#======================================================================
# Copyright (C) 2012 Diego Duclos
#
@@ -18,35 +18,70 @@
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
#======================================================================
import functools
import itertools
import json
import os
import sqlite3
import sys
# Add eos root path to sys.path so we can import ourselves
path = os.path.dirname(__file__)
sys.path.insert(0, os.path.realpath(os.path.join(path, '..')))
import json
import argparse
import itertools
ROOT_DIR = os.path.realpath(os.path.dirname(__file__))
DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
if ROOT_DIR not in sys.path:
sys.path.insert(0, ROOT_DIR)
GAMEDATA_SCHEMA_VERSION = 1
CATEGORIES_TO_REMOVE = [
30 # Apparel
]
def db_needs_update():
"""True if needs, false if it does not, none if we cannot check it."""
try:
with open(os.path.join(JSON_DIR, 'phobos', 'metadata.json')) as f:
data_version = next((r['field_value'] for r in json.load(f) if r['field_name'] == 'client_build'))
except KeyboardInterrupt:
raise
# If we have no source data - return None; should not update in this case
except:
return None
if not os.path.isfile(DB_PATH):
print('Gamedata DB not found')
return True
db_data_version = None
db_schema_version = None
try:
db = sqlite3.connect(DB_PATH)
cursor = db.cursor()
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'client_build\'')
for row in cursor:
db_data_version = int(row[0])
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'schema_version\'')
for row in cursor:
db_schema_version = int(row[0])
cursor.close()
db.close()
except KeyboardInterrupt:
raise
except:
print('Error when fetching gamedata DB metadata')
return True
if data_version != db_data_version:
print('Gamedata DB data version mismatch: needed {}, DB has {}'.format(data_version, db_data_version))
return True
if GAMEDATA_SCHEMA_VERSION != db_schema_version:
print('Gamedata DB schema version mismatch: needed {}, DB has {}'.format(GAMEDATA_SCHEMA_VERSION, db_schema_version))
return True
return False
def main(db, json_path):
if os.path.isfile(db):
os.remove(db)
jsonPath = os.path.expanduser(json_path)
def update_db():
# Import eos.config first and change it
import eos.config
eos.config.gamedata_connectionstring = db
eos.config.debug = False
print('Building gamedata DB...')
if os.path.isfile(DB_PATH):
os.remove(DB_PATH)
# Now thats done, we can import the eos modules using the config
import eos.db
import eos.gamedata
@@ -274,7 +309,7 @@ def main(db, json_path):
# Dump all data to memory so we can easely cross check ignored rows
for jsonName, (minerName, cls) in tables.items():
with open(os.path.join(jsonPath, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
tableData = json.load(f)
if jsonName in rowsInValues:
newTableData = []
@@ -373,7 +408,7 @@ def main(db, json_path):
eos.db.gamedata_session.add(instance)
# quick and dirty hack to get this data in
with open(os.path.join(jsonPath, 'fsd_binary', 'dynamicitemattributes.json'), encoding='utf-8') as f:
with open(os.path.join(JSON_DIR, 'fsd_binary', 'dynamicitemattributes.json'), encoding='utf-8') as f:
bulkdata = json.load(f)
for mutaID, data in bulkdata.items():
muta = eos.gamedata.DynamicItem()
@@ -395,20 +430,25 @@ def main(db, json_path):
attr.max = attrData['max']
eos.db.gamedata_session.add(attr)
# Add schema version to prevent further updates
metadata_schema_version = eos.gamedata.MetaData()
metadata_schema_version.field_name = 'schema_version'
metadata_schema_version.field_value = GAMEDATA_SCHEMA_VERSION
eos.db.gamedata_session.add(metadata_schema_version)
eos.db.gamedata_session.commit()
# CCP still has 5 subsystems assigned to T3Cs, even though only 4 are available / usable. They probably have some
# old legacy requirement or assumption that makes it difficult for them to change this value in the data. But for
# pyfa, we can do it here as a post-processing step
eos.db.gamedata_engine.execute('UPDATE dgmtypeattribs SET value = 4.0 WHERE attributeID = ?', (1367,))
for attr in eos.db.gamedata_session.query(eos.gamedata.Attribute).filter(eos.gamedata.Attribute.ID == 1367).all():
attr.value = 4.0
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(eos.gamedata.Item.name.like('%abyssal%')).all():
item.published = False
eos.db.gamedata_engine.execute('UPDATE invtypes SET published = 0 WHERE typeName LIKE \'%abyssal%\'')
# fix for #1722 until CCP gets their shit together
eos.db.gamedata_engine.execute('UPDATE invtypes SET typeName = \'Small Abyssal Energy Nosferatu\' WHERE typeID = ? AND typeName = ?', (48419, ''))
print()
for x in CATEGORIES_TO_REMOVE:
for x in [
30 # Apparel
]:
cat = eos.db.gamedata_session.query(eos.gamedata.Category).filter(eos.gamedata.Category.ID == x).first()
print ('Removing Category: {}'.format(cat.name))
eos.db.gamedata_session.delete(cat)
@@ -418,11 +458,6 @@ def main(db, json_path):
print('done')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='This scripts dumps effects from an sqlite cache dump to mongo')
parser.add_argument('-d', '--db', required=True, type=str, help='The sqlalchemy connectionstring, example: sqlite:///c:/tq.db')
parser.add_argument('-j', '--json', required=True, type=str, help='The path to the json dump')
args = parser.parse_args()
main(args.db, args.json)
update_db()

View File

@@ -18,6 +18,38 @@
# =============================================================================
import math
# Just copy-paste penalization chain calculation code (with some modifications,
# as multipliers arrive in different form) in here to not make actual attribute
# calculations slower than they already are due to extra function calls
def calculateMultiplier(multipliers):
"""
multipliers: dictionary in format:
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
"""
val = 1
for penalizedMultipliers in multipliers.values():
# A quick explanation of how this works:
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
# 2: The most significant bonuses take the smallest penalty,
# This means we'll have to sort
abssort = lambda _val: -abs(_val - 1)
l1.sort(key=abssort)
l2.sort(key=abssort)
# 3: The first module doesn't get penalized at all
# Any module after the first takes penalties according to:
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
for l in (l1, l2):
for i in range(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
return val
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
if distance is None:
@@ -31,3 +63,9 @@ def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedR
return 1
else:
return 0
def calculateLockTime(srcScanRes, tgtSigRadius):
if not srcScanRes or not tgtSigRadius:
return None
return min(40000 / srcScanRes / math.asinh(tgtSigRadius) ** 2, 30 * 60)

View File

@@ -92,7 +92,8 @@ def getItem(lookfor, eager=None):
else:
# Item names are unique, so we can use first() instead of one()
item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.name == lookfor).first()
itemNameMap[lookfor] = item.ID
if item is not None:
itemNameMap[lookfor] = item.ID
else:
raise TypeError("Need integer or string as argument")
return item
@@ -195,7 +196,8 @@ def getGroup(lookfor, eager=None):
else:
# Group names are unique, so we can use first() instead of one()
group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.name == lookfor).first()
groupNameMap[lookfor] = group.ID
if group is not None:
groupNameMap[lookfor] = group.ID
else:
raise TypeError("Need integer or string as argument")
return group
@@ -224,7 +226,8 @@ def getCategory(lookfor, eager=None):
# Category names are unique, so we can use first() instead of one()
category = gamedata_session.query(Category).options(*processEager(eager)).filter(
Category.name == lookfor).first()
categoryNameMap[lookfor] = category.ID
if category is not None:
categoryNameMap[lookfor] = category.ID
else:
raise TypeError("Need integer or string as argument")
return category
@@ -253,7 +256,8 @@ def getMetaGroup(lookfor, eager=None):
# MetaGroup names are unique, so we can use first() instead of one()
metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter(
MetaGroup.name == lookfor).first()
metaGroupNameMap[lookfor] = metaGroup.ID
if metaGroup is not None:
metaGroupNameMap[lookfor] = metaGroup.ID
else:
raise TypeError("Need integer or string as argument")
return metaGroup

View File

@@ -10683,6 +10683,7 @@ class Effect3526(BaseEffect):
Used by:
Ships from group: Force Recon Ship (8 of 9)
Ship: Venture
Skill: Cynosural Field Theory
"""

View File

@@ -17,7 +17,8 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
import collections
from collections.abc import MutableMapping
from copy import copy
from math import exp
@@ -96,7 +97,7 @@ class ChargeAttrShortcut:
return return_value or default
class ModifiedAttributeDict(collections.MutableMapping):
class ModifiedAttributeDict(MutableMapping):
overrides_enabled = False
class CalculationPlaceholder:

View File

@@ -78,6 +78,15 @@ class DamagePattern:
"exp" : "explosive"
}
@classmethod
def oneType(cls, damageType, amount=100):
pattern = DamagePattern()
pattern.update(amount if damageType == "em" else 0,
amount if damageType == "thermal" else 0,
amount if damageType == "kinetic" else 0,
amount if damageType == "explosive" else 0)
return pattern
@classmethod
def importPatterns(cls, text):
lines = re.split('[\n\r]+', text)

View File

@@ -21,13 +21,14 @@ import datetime
import time
from copy import deepcopy
from itertools import chain
from math import asinh, log, sqrt
from math import log, sqrt
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos import capSim
from eos.calc import calculateMultiplier, calculateLockTime
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
from eos.effectHandlerHelpers import (
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
@@ -1529,9 +1530,7 @@ class Fit:
def calculateLockTime(self, radius):
scanRes = self.ship.getModifiedItemAttr("scanResolution")
if scanRes is not None and scanRes > 0:
# Yes, this function returns time in seconds, not miliseconds.
# 40,000 is indeed the correct constant here.
return min(40000 / scanRes / asinh(radius) ** 2, 30 * 60)
return calculateLockTime(srcScanRes=scanRes, tgtSigRadius=radius)
else:
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
@@ -1626,6 +1625,22 @@ class Fit:
if ability.active:
yield fighter, ability
def getDampMultScanRes(self):
damps = []
for mod in self.activeModulesIter():
for effectName in ('remoteSensorDampFalloff', 'structureModuleEffectRemoteSensorDampener'):
if effectName in mod.item.effects:
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
if 'doomsdayAOEDamp' in mod.item.effects:
damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
for drone in self.activeDronesIter():
if 'remoteSensorDampEntity' in drone.item.effects:
damps.extend(drone.amountActive * ((drone.getModifiedItemAttr('scanResolutionBonus'), 'default'),))
mults = {}
for strength, stackingGroup in damps:
mults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
return calculateMultiplier(mults)
def __deepcopy__(self, memo=None):
fitCopy = Fit()
# Character and owner are not copied

View File

@@ -318,36 +318,68 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"energyDestabilizationRange", "empFieldRange",
"ecmBurstRange", "warpScrambleRange", "cargoScanRange",
"shipScanRange", "surveyScanRange")
maxRange = None
for attr in attrs:
maxRange = self.getModifiedItemAttr(attr, None)
if maxRange is not None:
return maxRange
if self.charge is not None:
try:
chargeName = self.charge.group.name
except AttributeError:
pass
else:
if chargeName in ("Scanner Probe", "Survey Probe"):
return None
break
if maxRange is not None:
if 'burst projector' in self.item.name.lower():
maxRange -= self.owner.ship.getModifiedItemAttr("radius")
return maxRange
missileMaxRangeData = self.missileMaxRangeData
if missileMaxRangeData is None:
return None
lowerRange, higherRange, higherChance = missileMaxRangeData
maxRange = lowerRange * (1 - higherChance) + higherRange * higherChance
return maxRange
@property
def missileMaxRangeData(self):
if self.charge is None:
return None
try:
chargeName = self.charge.group.name
except AttributeError:
pass
else:
if chargeName in ("Scanner Probe", "Survey Probe"):
return None
def calculateRange(maxVelocity, mass, agility, flightTime):
# Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
# D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
mass = self.getModifiedChargeAttr("mass")
agility = self.getModifiedChargeAttr("agility")
if maxVelocity and (flightTime or mass or agility):
accelTime = min(flightTime, mass * agility / 1000000)
# Average distance done during acceleration
duringAcceleration = maxVelocity / 2 * accelTime
# Distance done after being at full speed
fullSpeed = maxVelocity * (flightTime - accelTime)
maxRange = duringAcceleration + fullSpeed
if 'fofMissileLaunching' in self.charge.effects:
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
if rangeLimit:
maxRange = min(maxRange, rangeLimit)
return maxRange
accelTime = min(flightTime, mass * agility / 1000000)
# Average distance done during acceleration
duringAcceleration = maxVelocity / 2 * accelTime
# Distance done after being at full speed
fullSpeed = maxVelocity * (flightTime - accelTime)
maxRange = duringAcceleration + fullSpeed
return maxRange
maxVelocity = self.getModifiedChargeAttr("maxVelocity")
if not maxVelocity:
return None
shipRadius = self.owner.ship.getModifiedItemAttr("radius")
# Flight time has bonus based on ship radius, see https://github.com/pyfa-org/Pyfa/issues/2083
flightTime = floatUnerr(self.getModifiedChargeAttr("explosionDelay") / 1000 + shipRadius / maxVelocity)
mass = self.getModifiedChargeAttr("mass")
agility = self.getModifiedChargeAttr("agility")
lowerTime = math.floor(flightTime)
higherTime = math.ceil(flightTime)
lowerRange = calculateRange(maxVelocity, mass, agility, lowerTime)
higherRange = calculateRange(maxVelocity, mass, agility, higherTime)
# Fof range limit is supposedly calculated based on overview (surface-to-surface) range
if 'fofMissileLaunching' in self.charge.effects:
rangeLimit = self.getModifiedChargeAttr("maxFOFTargetRange")
if rangeLimit:
lowerRange = min(lowerRange, rangeLimit)
higherRange = min(higherRange, rangeLimit)
# Make range center-to-surface, as missiles spawn in the center of the ship
lowerRange = max(0, lowerRange - shipRadius)
higherRange = max(0, higherRange - shipRadius)
higherChance = flightTime - lowerTime
return lowerRange, higherRange, higherChance
@property
def falloff(self):

View File

@@ -46,11 +46,11 @@ class DmgTypes:
# Round for comparison's sake because often damage profiles are
# generated from data which includes float errors
return (
floatUnerr(self.em) == floatUnerr(other.em) and
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
floatUnerr(self.total) == floatUnerr(other.total))
floatUnerr(self.em) == floatUnerr(other.em) and
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
floatUnerr(self.total) == floatUnerr(other.total))
def __bool__(self):
return any((
@@ -110,9 +110,19 @@ class DmgTypes:
return self
def __repr__(self):
spec = ['em', 'thermal', 'kinetic', 'explosive', 'total']
spec = DmgTypes.names()
spec.append('total')
return makeReprStr(self, spec)
@staticmethod
def names(short=None, postProcessor=None):
value = ['em', 'th', 'kin', 'exp'] if short else ['em', 'thermal', 'kinetic', 'explosive']
if postProcessor:
value = [postProcessor(x) for x in value]
return value
class RRTypes:
"""Container for tank data stats."""
@@ -136,10 +146,10 @@ class RRTypes:
# Round for comparison's sake because often tanking numbers are
# generated from data which includes float errors
return (
floatUnerr(self.shield) == floatUnerr(other.shield) and
floatUnerr(self.armor) == floatUnerr(other.armor) and
floatUnerr(self.hull) == floatUnerr(other.hull) and
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
floatUnerr(self.shield) == floatUnerr(other.shield) and
floatUnerr(self.armor) == floatUnerr(other.armor) and
floatUnerr(self.hull) == floatUnerr(other.hull) and
floatUnerr(self.capacitor) == floatUnerr(other.capacitor))
def __bool__(self):
return any((self.shield, self.armor, self.hull, self.capacitor))
@@ -191,5 +201,17 @@ class RRTypes:
return self
def __repr__(self):
spec = ['shield', 'armor', 'hull', 'capacitor']
spec = RRTypes.names(False)
return makeReprStr(self, spec)
@staticmethod
def names(ehpOnly=True, postProcessor=None):
value = ['shield', 'armor', 'hull']
if not ehpOnly:
value.append('capacitor')
if postProcessor:
value = [postProcessor(x) for x in value]
return value

BIN
eve.db

Binary file not shown.

View File

@@ -18,40 +18,9 @@
# =============================================================================
import math
from service.settings import GraphSettings
# Just copy-paste penalization chain calculation code (with some modifications,
# as multipliers arrive in different form) in here to not make actual attribute
# calculations slower than they already are due to extra function calls
def calculateMultiplier(multipliers):
"""
multipliers: dictionary in format:
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
"""
val = 1
for penalizedMultipliers in multipliers.values():
# A quick explanation of how this works:
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
# 2: The most significant bonuses take the smallest penalty,
# This means we'll have to sort
abssort = lambda _val: -abs(_val - 1)
l1.sort(key=abssort)
l2.sort(key=abssort)
# 3: The first module doesn't get penalized at all
# Any module after the first takes penalties according to:
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
for l in (l1, l2):
for i in range(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
return val
def checkLockRange(src, distance):
if distance is None:
return True

View File

@@ -26,3 +26,5 @@ from . import fitCapacitor
from . import fitMobility
from . import fitWarpTime
from . import fitLockTime
# Hidden graphs, available via ctrl-alt-g
from . import fitEcmBurstScanresDamps

View File

@@ -29,6 +29,7 @@ from service.const import GraphCacheCleanupReason
class FitGraph(metaclass=ABCMeta):
# UI stuff
hidden = False
views = []
viewMap = {}

View File

@@ -152,18 +152,24 @@ def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
modRange = mod.maxRange
if modRange is None:
missileMaxRangeData = mod.missileMaxRangeData
if missileMaxRangeData is None:
return 0
if distance is not None and distance + src.getRadius() > modRange:
return 0
mult = _calcMissileFactor(
# The ranges already consider ship radius
lowerRange, higherRange, higherChance = missileMaxRangeData
if distance is None or distance <= lowerRange:
distanceFactor = 1
elif lowerRange < distance <= higherRange:
distanceFactor = higherChance
else:
distanceFactor = 0
applicationFactor = _calcMissileFactor(
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
return mult
return distanceFactor * applicationFactor
def getSmartbombMult(mod, distance):

View File

@@ -0,0 +1,24 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from .graph import FitEcmBurstScanresDampsGraph
FitEcmBurstScanresDampsGraph.register()

View File

@@ -0,0 +1,117 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
from eos.calc import calculateLockTime
from graphs.data.base import SmoothPointGetter
ECM_BURST_DURATION = 30
DRONE_LOCK_TIME = 2
class TgtScanRes2TgtLockTimeGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, src, tgt):
if miscParams['applyDamps']:
tgtScanResMult = src.item.getDampMultScanRes()
else:
tgtScanResMult = 1
return {
'tgtScanResMult': tgtScanResMult,
'sigRadius': src.item.ship.getModifiedItemAttr('signatureRadius')}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
scanRes = x
time = calculateLockTime(
srcScanRes=scanRes * commonData['tgtScanResMult'],
tgtSigRadius=commonData['sigRadius'])
return time
class TgtScanRes2TgtLockUptimeGetter(TgtScanRes2TgtLockTimeGetter):
def _calculatePoint(self, *args, **kwargs):
# Assuming you ECM burst every 30 seconds, find out how long you
# will be locked before you burst another time
lockTime = super()._calculatePoint(*args, **kwargs)
lockedTime = max(0, ECM_BURST_DURATION - lockTime)
return lockedTime
class SrcDmgBaseGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, src, tgt):
if miscParams['applyDamps']:
tgtScanResMult = src.item.getDampMultScanRes()
else:
tgtScanResMult = 1
return {
'tgtScanResMult': tgtScanResMult,
'srcSigRadius': src.item.ship.getModifiedItemAttr('signatureRadius'),
'srcEhp': sum(src.item.ehp.values()),
'srcDpsWeapon': src.item.getWeaponDps().total,
'srcDpsDrone': src.item.getDroneDps().total if miscParams['applyDrones'] else 0}
@staticmethod
def _calculateInflictedDamage(srcSigRadius, srcWeaponDps, srcDroneDps, srcEhp, tgtScanRes, tgtDps, uptimeAdjustment, uptimeAmountLimit):
lockTime = calculateLockTime(srcScanRes=tgtScanRes, tgtSigRadius=srcSigRadius)
lockUptime = max(0, ECM_BURST_DURATION - lockTime - uptimeAdjustment)
lockDowntime = ECM_BURST_DURATION - lockUptime
inflictedDmg = 0
remainingEhp = srcEhp
for i in range(int(uptimeAmountLimit)):
timeAliveUnderFire = min(lockUptime, remainingEhp / tgtDps)
timeAlive = lockDowntime + timeAliveUnderFire
remainingEhp -= lockUptime * tgtDps
inflictedDmg += timeAlive * srcWeaponDps
inflictedDmg += max(0, timeAlive - DRONE_LOCK_TIME - 1) * srcDroneDps
if remainingEhp <= 0:
break
return inflictedDmg
class TgtScanRes2SrcDmgGetter(SrcDmgBaseGetter):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
damage = self._calculateInflictedDamage(
srcSigRadius=commonData['srcSigRadius'],
srcWeaponDps=commonData['srcDpsWeapon'],
srcDroneDps=commonData['srcDpsDrone'],
srcEhp=commonData['srcEhp'],
tgtScanRes=x * commonData['tgtScanResMult'],
tgtDps=miscParams['tgtDps'],
uptimeAdjustment=miscParams['uptimeAdj'],
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
return damage
class TgtDps2SrcDmgGetter(SrcDmgBaseGetter):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
damage = self._calculateInflictedDamage(
srcSigRadius=commonData['srcSigRadius'],
srcWeaponDps=commonData['srcDpsWeapon'],
srcDroneDps=commonData['srcDpsDrone'],
srcEhp=commonData['srcEhp'],
tgtScanRes=miscParams['tgtScanRes'] * commonData['tgtScanResMult'],
tgtDps=x,
uptimeAdjustment=miscParams['uptimeAdj'],
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
return damage

View File

@@ -0,0 +1,65 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
"""
Disclaimer by kadesh: this graph was made to analyze my ECM burst + damp frig
concept. I do not think it is useful for regular player, so it is disabled.
Enable by setting config.experimentalFeatures = True.
"""
import math
from graphs.data.base import FitGraph, XDef, YDef, Input, InputCheckbox
from .getter import (
TgtScanRes2TgtLockTimeGetter, TgtScanRes2TgtLockUptimeGetter,
TgtScanRes2SrcDmgGetter, TgtDps2SrcDmgGetter)
class FitEcmBurstScanresDampsGraph(FitGraph):
# UI stuff
hidden = True
internalName = 'ecmBurstScanresDamps'
name = 'ECM Burst + Scanres Damps'
xDefs = [
XDef(handle='tgtDps', unit=None, label='Enemy DPS', mainInput=('tgtDps', None)),
XDef(handle='tgtScanRes', unit='mm', label='Enemy scanres', mainInput=('tgtScanRes', 'mm'))]
yDefs = [
YDef(handle='srcDmg', unit=None, label='Damage inflicted'),
YDef(handle='tgtLockTime', unit='s', label='Lock time'),
YDef(handle='tgtLockUptime', unit='s', label='Lock uptime')]
inputs = [
Input(handle='tgtScanRes', unit='mm', label='Enemy scanres', iconID=74, defaultValue=700, defaultRange=(100, 1000)),
Input(handle='tgtDps', unit=None, label='Enemy DPS', iconID=1432, defaultValue=200, defaultRange=(100, 600)),
Input(handle='uptimeAdj', unit='s', label='Uptime adjustment', iconID=1392, defaultValue=1, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))]),
Input(handle='uptimeAmtLimit', unit=None, label='Max amount of uptimes', iconID=1397, defaultValue=3, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))])]
checkboxes = [
InputCheckbox(handle='applyDamps', label='Apply sensor dampeners', defaultValue=True),
InputCheckbox(handle='applyDrones', label='Use drones', defaultValue=True, conditions=[(None, ('srcDmg', None))])]
srcExtraCols = ('SigRadius', 'Damp ScanRes')
# Calculation stuff
_limiters = {'tgtScanRes': lambda src, tgt: (1, math.inf)}
_getters = {
('tgtScanRes', 'tgtLockTime'): TgtScanRes2TgtLockTimeGetter,
('tgtScanRes', 'tgtLockUptime'): TgtScanRes2TgtLockUptimeGetter,
('tgtScanRes', 'srcDmg'): TgtScanRes2SrcDmgGetter,
('tgtDps', 'srcDmg'): TgtDps2SrcDmgGetter}

View File

@@ -20,8 +20,8 @@
import math
from eos.calc import calculateRangeFactor
from graphs.calc import calculateMultiplier, checkLockRange, checkDroneControlRange
from eos.calc import calculateMultiplier, calculateRangeFactor
from graphs.calc import checkLockRange, checkDroneControlRange
from graphs.data.base import SmoothPointGetter

View File

@@ -20,7 +20,7 @@
import math
from graphs.data.base import FitGraph, Input, XDef, YDef
from graphs.data.base import FitGraph, XDef, YDef, Input
from .getter import TgtSigRadius2LockTimeGetter

View File

@@ -43,7 +43,7 @@ REDRAW_DELAY = 500
class GraphFrame(AuxiliaryFrame):
def __init__(self, parent):
def __init__(self, parent, includeHidden=False):
if not canvasPanel.graphFrame_enabled:
pyfalog.warning('Matplotlib is not enabled. Skipping initialization.')
return
@@ -74,6 +74,8 @@ class GraphFrame(AuxiliaryFrame):
# Setup - graph selector
for view in FitGraph.views:
if view.hidden and not includeHidden:
continue
self.graphSelection.Append(view.name, view())
self.graphSelection.SetSelection(0)
self.ctrlPanel.updateControls(layout=False)
@@ -101,9 +103,9 @@ class GraphFrame(AuxiliaryFrame):
self.draw()
@classmethod
def openOne(cls, parent):
def openOne(cls, parent, *args, **kwargs):
if canvasPanel.graphFrame_enabled:
super().openOne(parent)
super().openOne(parent, *args, **kwargs)
def UpdateWindowSize(self):
curW, curH = self.GetSize()

View File

@@ -18,11 +18,11 @@
# =============================================================================
from eos.calc import calculateMultiplier
from eos.saveddata.damagePattern import DamagePattern
from eos.saveddata.fit import Fit
from eos.saveddata.targetProfile import TargetProfile
from service.const import TargetResistMode
from .calc import calculateMultiplier
class BaseWrapper:

View File

@@ -19,7 +19,12 @@
import config
versionString = "{0}".format(config.version)
try:
versionString = "{0}".format(config.getVersion())
except NameError:
# is caught in case we run test and there are no config values initialized
versionString = "0.0"
licenses = (
"pyfa is released under GNU GPLv3 - see included LICENSE file",
"All EVE-Online related materials are property of CCP hf.",

View File

@@ -53,10 +53,10 @@ class AuxiliaryFrame(wx.Frame):
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
@classmethod
def openOne(cls, parent):
def openOne(cls, parent, *args, **kwargs):
"""If window is open and alive - raise it, open otherwise"""
if not cls._instance:
frame = cls(parent)
frame = cls(parent, *args, **kwargs)
cls._instance = frame
frame.Show()
else:

View File

@@ -33,12 +33,17 @@ pyfalog = Logger(__name__)
class BitmapLoader:
try:
archive = zipfile.ZipFile(config.imgsZIP, 'r')
pyfalog.info("Using zipped image files.")
except (IOError, TypeError):
# Can be None if we're running from tests
if config.imgsZIP is None:
pyfalog.info("Using local image files.")
archive = None
else:
try:
archive = zipfile.ZipFile(config.imgsZIP, 'r')
pyfalog.info("Using zipped image files.")
except (IOError, TypeError):
pyfalog.info("Using local image files.")
archive = None
cached_bitmaps = OrderedDict()
dont_use_cached_bitmaps = False
@@ -84,7 +89,7 @@ class BitmapLoader:
@classmethod
def loadBitmap(cls, name, location):
if cls.scaling_factor is None:
cls.scaling_factor = int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
cls.scaling_factor = 1 if 'wxGTK' in wx.PlatformInfo else int(wx.GetApp().GetTopWindow().GetContentScaleFactor())
scale = cls.scaling_factor
filename, img = cls.loadScaledBitmap(name, location, scale)

View File

@@ -0,0 +1,60 @@
# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
# noinspection PyPackageRequirements
import wx
from eos.saveddata.fit import Fit
from graphs.wrapper import BaseWrapper
from gui.bitmap_loader import BitmapLoader
from eos.utils.float import floatUnerr
from gui.utils.numberFormatter import formatAmount
from gui.viewColumn import ViewColumn
class DampScanResColumn(ViewColumn):
name = 'Damp ScanRes'
def __init__(self, fittingView, params):
ViewColumn.__init__(self, fittingView)
self.imageId = fittingView.imageList.GetImageIndex(74, 'icons')
self.bitmap = BitmapLoader.getBitmap(74, 'icons')
self.mask = wx.LIST_MASK_IMAGE
def getText(self, stuff):
if isinstance(stuff, BaseWrapper):
stuff = stuff.item
mult = 1
if isinstance(stuff, Fit):
mult = floatUnerr(stuff.getDampMultScanRes())
if mult == 1:
text = ''
else:
text = '{}%'.format(formatAmount((mult - 1) * 100, 3, 0, 0, forceSign=True))
return text
def getImageId(self, stuff):
return -1
def getToolTip(self, stuff):
return 'Scan resolution dampening'
DampScanResColumn.register()

View File

@@ -24,7 +24,7 @@ from eos.saveddata.mode import Mode
from service.attribute import Attribute
from gui.viewColumn import ViewColumn
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundToPrec
class MaxRange(ViewColumn):
@@ -77,7 +77,21 @@ class MaxRange(ViewColumn):
return ("displayName", bool, False), ("showIcon", bool, True)
def getToolTip(self, mod):
return "Optimal + Falloff"
lines = []
missileRangeData = mod.missileMaxRangeData if hasattr(mod, "missileMaxRangeData") else None
if missileRangeData is not None:
lines.append('Missile flight range')
lowerRange, higherRange, higherChance = missileRangeData
if roundToPrec(higherChance, 3) not in (0, 1):
lines.append('{}% chance to fly {}m'.format(
formatAmount((1 - higherChance) * 100, prec=3, lowest=0, highest=0),
formatAmount(lowerRange, prec=3, lowest=0, highest=3)))
lines.append('{}% chance to fly {}m'.format(
formatAmount(higherChance * 100, prec=3, lowest=0, highest=0),
formatAmount(higherRange, prec=3, lowest=0, highest=3)))
else:
lines.append("Optimal + Falloff")
return '\n'.join(lines)
MaxRange.register()

View File

@@ -468,6 +468,8 @@ class FittingView(d.Display):
elif item.isModule:
if mstate.GetModifiers() == wx.MOD_ALT:
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID))
elif dstPos is not None:
self.mainFrame.command.Submit(cmd.GuiReplaceLocalModuleCommand(fitID=fitID, itemID=itemID, positions=[dstPos]))
else:
self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID))
elif item.isSubsystem:

View File

@@ -40,6 +40,7 @@ class CopySelectDialog(wx.Dialog):
copyFormatEsi = 3
copyFormatMultiBuy = 4
copyFormatEfs = 5
copyFormatFitStats = 6
def __init__(self, parent):
super().__init__(parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
@@ -50,7 +51,8 @@ class CopySelectDialog(wx.Dialog):
CopySelectDialog.copyFormatDna : self.exportDna,
CopySelectDialog.copyFormatEsi : self.exportEsi,
CopySelectDialog.copyFormatMultiBuy: self.exportMultiBuy,
CopySelectDialog.copyFormatEfs : self.exportEfs
CopySelectDialog.copyFormatEfs : self.exportEfs,
CopySelectDialog.copyFormatFitStats: self.exportFitStats
}
self.mainFrame = parent
@@ -62,6 +64,7 @@ class CopySelectDialog(wx.Dialog):
("ESI", (CopySelectDialog.copyFormatEsi, None)),
("DNA", (CopySelectDialog.copyFormatDna, DNA_OPTIONS)),
("EFS", (CopySelectDialog.copyFormatEfs, None)),
("Stats", (CopySelectDialog.copyFormatFitStats, None)),
# ("XML", (CopySelectDialog.copyFormatXml, None)),
))
@@ -71,7 +74,7 @@ class CopySelectDialog(wx.Dialog):
continue
defaultFormatOptions[formatId] = {opt[0]: opt[3] for opt in formatOptions}
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": 0, "options": defaultFormatOptions})
self.settings = SettingsProvider.getInstance().getSettings("pyfaExport", {"format": self.copyFormatEft, "options": defaultFormatOptions})
# Options used to be stored as int (EFT export options only),
# overwrite them with new format when needed
if isinstance(self.settings["options"], int):
@@ -80,6 +83,7 @@ class CopySelectDialog(wx.Dialog):
self.options = {}
initialized = False
self.copyFormat = self.copyFormatEft
for formatName, formatData in self.copyFormats.items():
formatId, formatOptions = formatData
if not initialized:
@@ -117,7 +121,8 @@ class CopySelectDialog(wx.Dialog):
self.Center()
def Validate(self):
# Since this dialog is shown through aa ShowModal(), we hook into the Validate function to veto the closing of the dialog until we're ready.
# Since this dialog is shown through as ShowModal(),
# we hook into the Validate function to veto the closing of the dialog until we're ready.
# This always returns False, and when we're ready will EndModal()
selected = self.GetSelected()
options = self.GetOptions()
@@ -185,3 +190,10 @@ class CopySelectDialog(wx.Dialog):
def exportEfs(self, options, callback):
fit = getFit(self.mainFrame.getActiveFit())
EfsPort.exportEfs(fit, 0, callback)
# noinspection PyUnusedLocal
def exportFitStats(self, options, callback):
""" Puts fit stats in textual format into the clipboard """
fit = getFit(self.mainFrame.getActiveFit())
Port.exportFitStats(fit, callback)

View File

@@ -204,6 +204,7 @@ class MainFrame(wx.Frame):
self.addPageId = wx.NewId()
self.closePageId = wx.NewId()
self.closeAllPagesId = wx.NewId()
self.hiddenGraphsId = wx.NewId()
self.widgetInspectMenuID = wx.NewId()
self.SetMenuBar(MainMenuBar(self))
@@ -423,6 +424,9 @@ class MainFrame(wx.Frame):
def OnShowGraphFrame(self, event):
GraphFrame.openOne(self)
def OnShowGraphFrameHidden(self, event):
GraphFrame.openOne(self, includeHidden=True)
def OnShowDevTools(self, event):
DevTools.openOne(parent=self)
@@ -551,6 +555,7 @@ class MainFrame(wx.Frame):
# Graphs
self.Bind(wx.EVT_MENU, self.OnShowGraphFrame, id=menuBar.graphFrameId)
self.Bind(wx.EVT_MENU, self.OnShowGraphFrameHidden, id=self.hiddenGraphsId)
toggleSearchBoxId = wx.NewId()
toggleShipMarketId = wx.NewId()
@@ -576,6 +581,9 @@ class MainFrame(wx.Frame):
(wx.ACCEL_CTRL, wx.WXK_F4, self.closePageId),
(wx.ACCEL_CMD, ord("W"), self.closePageId),
(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId),
(wx.ACCEL_CMD | wx.ACCEL_ALT, ord("G"), self.hiddenGraphsId),
(wx.ACCEL_CTRL | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId),
(wx.ACCEL_CTRL | wx.ACCEL_ALT, wx.WXK_F4, self.closeAllPagesId),
(wx.ACCEL_CMD | wx.ACCEL_ALT, ord("W"), self.closeAllPagesId),

View File

@@ -77,6 +77,7 @@ from gui.builtinViewColumns import ( # noqa: E402, F401
baseIcon,
baseName,
capacitorUse,
dampScanRes,
graphColor,
graphLightness,
graphLineStyle,

View File

@@ -26,6 +26,7 @@ from optparse import AmbiguousOptionError, BadOptionError, OptionParser
import config
from service.prereqsCheck import PreCheckException, PreCheckMessage, version_block, version_precheck
from db_update import db_needs_update, update_db
# ascii_text = '''
@@ -75,6 +76,7 @@ parser.add_option("-p", "--profile", action="store", dest="profile_path", help="
(options, args) = parser.parse_args()
if __name__ == "__main__":
try:
@@ -116,6 +118,9 @@ if __name__ == "__main__":
else:
pyfalog.info("Running in a thawed state.")
if db_needs_update() is True:
update_db()
# Lets get to the good stuff, shall we?
import eos.db
import eos.events # todo: move this to eos initialization?

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
wget "https://www.python.org/ftp/python/${PYTHON}/python-${PYTHON}-macosx10.6.pkg"
sudo installer -pkg python-${PYTHON}-macosx10.6.pkg -target /
sudo python3 -m ensurepip
sudo python3 -m ensurepip --upgrade
# A manual check that the correct version of Python is running.
python3 --version
pip3 install -r requirements.txt
python3 -m pip install -r requirements.txt

View File

@@ -112,11 +112,14 @@ class Fit:
ship = eos.db.getItem(shipID)
if ship is not None:
shipMap[shipID] = (ship.name, ship.getShortName())
fitsToPurge = set()
for fit in fits:
try:
fit.shipName, fit.shipNameShort = shipMap[fit.shipID]
except KeyError:
pass
except (KeyError, TypeError):
fitsToPurge.add(fit)
for fit in fitsToPurge:
fits.remove(fit)
return fits
@staticmethod

View File

@@ -24,7 +24,7 @@ import yaml
from .jargon import Jargon
from .resources import DEFAULT_DATA, DEFAULT_HEADER
JARGON_PATH = os.path.join(config.savePath, 'jargon.yaml')
JARGON_PATH = os.path.join(config.savePath, 'jargon.yaml') if config.savePath is not None else None
class JargonLoader:
@@ -44,14 +44,15 @@ class JargonLoader:
def _load_jargon(self):
jargondata = yaml.load(DEFAULT_DATA, Loader=yaml.SafeLoader)
with open(JARGON_PATH) as f:
userdata = yaml.load(f, Loader=yaml.SafeLoader)
jargondata.update(userdata)
if JARGON_PATH is not None:
with open(JARGON_PATH) as f:
userdata = yaml.load(f, Loader=yaml.SafeLoader)
jargondata.update(userdata)
self.jargon_mtime = self._get_jargon_file_mtime()
self._jargon = Jargon(jargondata)
def _get_jargon_file_mtime(self) -> int:
if not os.path.exists(self.jargon_path):
if self.jargon_path is None or not os.path.exists(self.jargon_path):
return 0
return os.stat(self.jargon_path).st_mtime
@@ -82,4 +83,5 @@ class JargonLoader:
return JargonLoader._instance
JargonLoader.init_user_jargon(JARGON_PATH)
if JARGON_PATH is not None:
JargonLoader.init_user_jargon(JARGON_PATH)

View File

@@ -1 +1 @@
__all__ = ['evemarketer', 'evemarketdata', 'evepraisal']
__all__ = ['evemarketer', 'evepraisal', 'evemarketdata']

View File

@@ -66,11 +66,15 @@ class EvePraisal:
try:
typeID = int(itemData['typeID'])
price = itemData['prices']['sell']['min']
orderCount = itemData['prices']['sell']['order_count']
except (KeyError, TypeError):
continue
# evepraisal returns 0 if price data doesn't even exist for the item
if price == 0:
continue
# evepraisal seems to provide price for some items despite having no orders up
if orderCount < 1:
continue
priceMap[typeID].update(PriceStatus.fetchSuccess, price)
del priceMap[typeID]

View File

@@ -5,7 +5,7 @@ from numbers import Number
from logbook import Logger
import eos.db
from config import version as pyfaVersion
from config import getVersion
from service.fit import Fit
from service.market import Market
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
@@ -23,6 +23,7 @@ from gui.fitCommands.helpers import ModuleInfo
pyfalog = Logger(__name__)
pyfaVersion = getVersion()
class EfsPort:

View File

@@ -18,8 +18,8 @@
# =============================================================================
import collections
import json
from collections import defaultdict
from logbook import Logger
@@ -61,7 +61,7 @@ def exportESI(ofit, callback):
# max fit name length is 50 characters
# Most keys are created simply because they are required, but bogus data is okay
nested_dict = lambda: collections.defaultdict(nested_dict)
nested_dict = lambda: defaultdict(nested_dict)
fit = nested_dict()
sFit = svcFit.getInstance()

View File

@@ -39,6 +39,7 @@ from service.port.eft import (
from service.port.esi import exportESI, importESI
from service.port.multibuy import exportMultiBuy
from service.port.shared import IPortUser, UserCancelException, processing_notify
from service.port.shipstats import exportFitStats
from service.port.xml import importXml, exportXml
from service.port.muta import parseMutant
@@ -46,7 +47,7 @@ from service.port.muta import parseMutant
pyfalog = Logger(__name__)
# 2017/04/05 NOTE: simple validation, for xml file
RE_XML_START = r'<\?xml\s+version="1.0"\s*\?>'
RE_XML_START = r'<\?xml\s+version="1.0"[^<>]*\?>'
class Port:
@@ -181,7 +182,8 @@ class Port:
pyfalog.critical(e)
# TypeError: not all arguments converted during string formatting
# return False, "Unknown Error while processing {0}" % path
return False, "Unknown error while processing %s\n\n Error: %s" % (path, e.message)
return False, "Unknown error while processing {}\n\n Error: {} {}".format(
path, type(e).__name__, getattr(e, 'message', ''))
return True, fit_list
@@ -317,3 +319,7 @@ class Port:
@staticmethod
def exportMultiBuy(fit, options, callback=None):
return exportMultiBuy(fit, options, callback=callback)
@staticmethod
def exportFitStats(fit, callback=None):
return exportFitStats(fit, callback=callback)

View File

@@ -82,9 +82,13 @@ def fetchItem(typeName, eagerCat=False):
eager = 'group.category' if eagerCat else None
try:
item = sMkt.getItem(typeName, eager=eager)
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning('service.port.shared: unable to fetch item "{}"'.format(typeName))
return None
if item is None:
return None
if sMkt.getPublicityByItem(item):
return item
else:

209
service/port/shipstats.py Normal file
View File

@@ -0,0 +1,209 @@
from functools import reduce
from eos.saveddata.damagePattern import DamagePattern
from eos.utils.stats import RRTypes, DmgTypes
from gui.utils.numberFormatter import formatAmount
tankTypes = RRTypes.names()
damageTypes = DmgTypes.names()
damagePatterns = [DamagePattern.oneType(damageType) for damageType in damageTypes]
damageTypeResonanceNames = [damageType.capitalize() + "DamageResonance" for damageType in damageTypes]
resonanceNames = {tankTypes[0]: [tankTypes[0] + s for s in damageTypeResonanceNames],
tankTypes[1]: [tankTypes[1] + s for s in damageTypeResonanceNames],
tankTypes[2]: [s[0].lower() + s[1:] for s in damageTypeResonanceNames]}
def firepowerSection(fit):
""" Returns the text of the firepower section"""
totalDps = fit.getTotalDps().total
weaponDps = fit.getWeaponDps().total
droneDps = fit.getDroneDps().total
totalVolley = fit.getTotalVolley().total
firepower = [totalDps, weaponDps, droneDps, totalVolley]
firepowerStr = [formatAmount(dps, 3, 0, 0) for dps in firepower]
# showWeaponAndDroneDps = (weaponDps > 0) and (droneDps > 0)
if sum(firepower) == 0:
return ""
return "DPS: {} (".format(firepowerStr[0]) + \
("Weapon: {}, Drone: {}, ".format(*firepowerStr[1:3])) + \
("Volley: {})\n".format(firepowerStr[3]))
def tankSection(fit):
""" Returns the text of the tank section"""
ehp = [fit.ehp[tank] for tank in tankTypes] if fit.ehp is not None else [0, 0, 0]
ehp.append(sum(ehp))
ehpStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehp]
resists = {tankType: [1 - fit.ship.getModifiedItemAttr(s) for s in resonanceNames[tankType]] for tankType in tankTypes}
ehpAgainstDamageType = [sum(pattern.calculateEhp(fit).values()) for pattern in damagePatterns]
ehpAgainstDamageTypeStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehpAgainstDamageType]
# not used for now. maybe will be improved later
# def formattedOutput():
# return \
# " {:>7} {:>7} {:>7} {:>7} {:>7}\n".format("TOTAL", "EM", "THERM", "KIN", "EXP") + \
# "EHP {:>7} {:>7} {:>7} {:>7} {:>7}\n".format(ehpStr[3], *ehpAgainstDamageTypeStr) + \
# "Shield {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[0], *resists["shield"]) + \
# "Armor {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[1], *resists["armor"]) + \
# "Hull {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[2], *resists["hull"])
def generalOutput():
rowNames = ["EHP"]
rowNames.extend(RRTypes.names(postProcessor=lambda v: v.capitalize()))
colNames = DmgTypes.names(short=True, postProcessor=lambda v: " " + v.capitalize())
colNames[0] = colNames[0][1::]
outputScheme = []
for index, rowName in enumerate(rowNames):
row = rowName + ": {:>} ("
subsValue = " {:.0%}," if index > 0 else " {:>},"
row += ''.join([(colName + ":" + subsValue) for colName in colNames])
row = row[:-1:] + ")\n"
outputScheme.append(row)
return \
outputScheme[0].format(ehpStr[3], *ehpAgainstDamageTypeStr) + \
outputScheme[1].format(ehpStr[0], *resists["shield"]) + \
outputScheme[2].format(ehpStr[1], *resists["armor"]) + \
outputScheme[3].format(ehpStr[2], *resists["hull"])
# return \
# "EHP: {:>} (Em: {:>}, Th: {:>}, Kin: {:>}, Exp: {:>})\n".format(ehpStr[3], *ehpAgainstDamageTypeStr) + \
# "Shield: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%})\n".format(ehpStr[0], *resists["shield"]) + \
# "Armor: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%})\n".format(ehpStr[1], *resists["armor"]) + \
# "Hull: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%})\n".format(ehpStr[2], *resists["hull"])
return generalOutput()
def _addFormattedColumn(value, name, header, linesList, repStr):
if value:
header += "{:>7} ".format(name)
linesList = [line + "{:>7} ".format(rep) for line, rep in zip(linesList, repStr)]
return header, linesList
def repsSection(fit):
""" Returns the text of the repairs section"""
selfRep = [fit.effectiveTank[tankType + "Repair"] for tankType in tankTypes]
sustainRep = [fit.effectiveSustainableTank[tankType + "Repair"] for tankType in tankTypes]
remoteRepObj = fit.getRemoteReps()
remoteRep = [remoteRepObj.shield, remoteRepObj.armor, remoteRepObj.hull]
shieldRegen = [fit.effectiveSustainableTank["passiveShield"], 0, 0]
shieldRechargeModuleMultipliers = [module.item.attributes["shieldRechargeRateMultiplier"].value for module in
fit.modules if
module.item and "shieldRechargeRateMultiplier" in module.item.attributes]
shieldRechargeMultiplierByModules = reduce(lambda x, y: x * y, shieldRechargeModuleMultipliers, 1)
if shieldRechargeMultiplierByModules >= 0.9: # If the total affect of modules on the shield recharge is negative or insignificant, we don't care about it
shieldRegen[0] = 0
totalRep = list(zip(selfRep, remoteRep, shieldRegen))
totalRep = list(map(sum, totalRep))
selfRep.append(sum(selfRep))
sustainRep.append(sum(sustainRep))
remoteRep.append(sum(remoteRep))
shieldRegen.append(sum(shieldRegen))
totalRep.append(sum(totalRep))
totalSelfRep = selfRep[-1]
totalRemoteRep = remoteRep[-1]
totalShieldRegen = shieldRegen[-1]
text = ""
if sum(totalRep) > 0: # Most commonly, there are no reps at all; then we skip this section
singleTypeRep = None
singleTypeRepName = None
if totalRemoteRep == 0 and totalShieldRegen == 0: # Only self rep
singleTypeRep = selfRep[:-1]
singleTypeRepName = "Self"
if totalSelfRep == 0 and totalShieldRegen == 0: # Only remote rep
singleTypeRep = remoteRep[:-1]
singleTypeRepName = "Remote"
if totalSelfRep == 0 and totalRemoteRep == 0: # Only shield regen
singleTypeRep = shieldRegen[:-1]
singleTypeRepName = "Regen"
if singleTypeRep and sum(
x > 0 for x in singleTypeRep) == 1: # Only one type of reps and only one tank type is repaired
index = next(i for i, v in enumerate(singleTypeRep) if v > 0)
if singleTypeRepName == "Regen":
text += "Shield regeneration: {} EHP/s".format(formatAmount(singleTypeRep[index], 3, 0, 9))
else:
text += "{} {} repair: {} EHP/s".format(singleTypeRepName, tankTypes[index],
formatAmount(singleTypeRep[index], 3, 0, 9))
if (singleTypeRepName == "Self") and (sustainRep[index] != singleTypeRep[index]):
text += " (Sustained: {} EHP/s)".format(formatAmount(sustainRep[index], 3, 0, 9))
text += "\n"
else: # Otherwise show a table
selfRepStr = [formatAmount(rep, 3, 0, 9) for rep in selfRep]
sustainRepStr = [formatAmount(rep, 3, 0, 9) for rep in sustainRep]
remoteRepStr = [formatAmount(rep, 3, 0, 9) for rep in remoteRep]
shieldRegenStr = [formatAmount(rep, 3, 0, 9) if rep != 0 else "" for rep in shieldRegen]
totalRepStr = [formatAmount(rep, 3, 0, 9) for rep in totalRep]
lines = RRTypes.names(postProcessor=lambda v: v.capitalize())
lines.append("Total")
lines = ["{:<8}".format(line) for line in lines]
showSelfRepColumn = totalSelfRep > 0
showSustainRepColumn = sustainRep != selfRep
showRemoteRepColumn = totalRemoteRep > 0
showShieldRegenColumn = totalShieldRegen > 0
header = "REPS "
header, lines = _addFormattedColumn(
(showSelfRepColumn + showSustainRepColumn + showRemoteRepColumn + showShieldRegenColumn > 1),
"TOTAL", header, lines, totalRepStr)
header, lines = _addFormattedColumn(showSelfRepColumn, "SELF", header, lines, selfRepStr)
header, lines = _addFormattedColumn(showSustainRepColumn, "SUST", header, lines, sustainRepStr)
header, lines = _addFormattedColumn(showRemoteRepColumn, "REMOTE", header, lines, remoteRepStr)
header, lines = _addFormattedColumn(showShieldRegenColumn, "REGEN", header, lines, shieldRegenStr)
text += header + "\n"
repsByTank = zip(totalRep, selfRep, sustainRep, remoteRep, shieldRegen)
for line in lines:
reps = next(repsByTank)
if sum(reps) > 0:
text += line + "\n"
return text
def miscSection(fit):
text = ""
text += "Speed: {} m/s\n".format(formatAmount(fit.maxSpeed, 3, 0, 0))
text += "Signature: {} m\n".format(formatAmount(fit.ship.getModifiedItemAttr("signatureRadius"), 3, 0, 9))
text += "Capacitor: {} GJ".format(formatAmount(fit.ship.getModifiedItemAttr("capacitorCapacity"), 3, 0, 9))
capState = fit.capState
if fit.capStable:
text += " (Stable at {0:.0f}%)".format(capState)
else:
text += " (Lasts {})".format("%ds" % capState if capState <= 60 else "%dm%ds" % divmod(capState, 60))
text += "\n"
text += "Targeting range: {} km\n".format(formatAmount(fit.maxTargetRange / 1000, 3, 0, 0))
text += "Scan resolution: {0:.0f} mm\n".format(fit.ship.getModifiedItemAttr("scanResolution"))
text += "Sensor strength: {}\n".format(formatAmount(fit.scanStrength, 3, 0, 0))
return text
def exportFitStats(fit, callback):
"""
Returns the text of the stats export of the given fit
"""
sections = filter(None, (firepowerSection(fit), # Prune empty sections
tankSection(fit),
repsSection(fit),
miscSection(fit)))
text = "{} ({})\n".format(fit.name, fit.ship.name) + "\n"
text += "\n".join(sections)
if callback:
callback(text)
else:
return text

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,524 @@
[
{
"dataID": 16545434,
"description": "Meter",
"descriptionID": 77965,
"displayName": "m",
"displayNameID": 78005,
"unitID": 1,
"unitName": "Length"
},
{
"dataID": 16545435,
"description": "Kilogram",
"descriptionID": 77966,
"displayName": "kg",
"displayNameID": 78006,
"unitID": 2,
"unitName": "Mass"
},
{
"dataID": 16545436,
"description": "Second",
"descriptionID": 77967,
"displayName": "sec",
"displayNameID": 78007,
"unitID": 3,
"unitName": "Time"
},
{
"dataID": 16545437,
"description": "Ampere",
"descriptionID": 77968,
"displayName": "A",
"displayNameID": 78008,
"unitID": 4,
"unitName": "Electric Current"
},
{
"dataID": 16545438,
"description": "Kelvin",
"descriptionID": 77969,
"displayName": "K",
"displayNameID": 78009,
"unitID": 5,
"unitName": "Temperature"
},
{
"dataID": 16545439,
"description": "Mole",
"descriptionID": 77970,
"displayName": "mol",
"displayNameID": 78010,
"unitID": 6,
"unitName": "Amount Of Substance"
},
{
"dataID": 16545440,
"description": "Candela",
"descriptionID": 77971,
"displayName": "cd",
"displayNameID": 78011,
"unitID": 7,
"unitName": "Luminous Intensity"
},
{
"dataID": 16545441,
"description": "Square meter",
"descriptionID": 77972,
"displayName": "m2",
"displayNameID": 78012,
"unitID": 8,
"unitName": "Area"
},
{
"dataID": 16545442,
"description": "Cubic meter",
"descriptionID": 77973,
"displayName": "m3",
"displayNameID": 78013,
"unitID": 9,
"unitName": "Volume"
},
{
"dataID": 16545443,
"description": "Meter per second",
"descriptionID": 77974,
"displayName": "m/sec",
"displayNameID": 78014,
"unitID": 10,
"unitName": "Speed"
},
{
"dataID": 16545444,
"description": "Meter per second squared",
"descriptionID": 77975,
"displayName": "m/sec",
"displayNameID": 78015,
"unitID": 11,
"unitName": "Acceleration"
},
{
"dataID": 16545445,
"description": "Reciprocal meter",
"descriptionID": 77976,
"displayName": "m-1",
"displayNameID": 78016,
"unitID": 12,
"unitName": "Wave Number"
},
{
"dataID": 16545446,
"description": "Kilogram per cubic meter",
"descriptionID": 77977,
"displayName": "kg/m3",
"displayNameID": 78017,
"unitID": 13,
"unitName": "Mass Density"
},
{
"dataID": 16545447,
"description": "Cubic meter per kilogram",
"descriptionID": 77978,
"displayName": "m3/kg",
"displayNameID": 78018,
"unitID": 14,
"unitName": "Specific Volume"
},
{
"dataID": 16545448,
"description": "Ampere per square meter",
"descriptionID": 77979,
"displayName": "A/m2",
"displayNameID": 78019,
"unitID": 15,
"unitName": "Current Density"
},
{
"dataID": 16545449,
"description": "Ampere per meter",
"descriptionID": 77980,
"displayName": "A/m",
"displayNameID": 78020,
"unitID": 16,
"unitName": "Magnetic Field Strength"
},
{
"dataID": 16545450,
"description": "Mole per cubic meter",
"descriptionID": 77981,
"displayName": "mol/m3",
"displayNameID": 78021,
"unitID": 17,
"unitName": "Amount-Of-Substance Concentration"
},
{
"dataID": 16545451,
"description": "Candela per square meter",
"descriptionID": 77982,
"displayName": "cd/m2",
"displayNameID": 78022,
"unitID": 18,
"unitName": "Luminance"
},
{
"dataID": 16545452,
"description": "Kilogram per kilogram, which may be represented by the number 1",
"descriptionID": 77983,
"displayName": "kg/kg = 1",
"displayNameID": 78023,
"unitID": 19,
"unitName": "Mass Fraction"
},
{
"dataID": 16545453,
"description": "",
"descriptionID": null,
"displayName": "s",
"displayNameID": 78024,
"unitID": 101,
"unitName": "Milliseconds"
},
{
"dataID": 16545454,
"description": "",
"descriptionID": null,
"displayName": "mm",
"displayNameID": 78025,
"unitID": 102,
"unitName": "Millimeters"
},
{
"dataID": 13353825,
"description": "",
"descriptionID": null,
"displayName": "",
"displayNameID": null,
"unitID": 103,
"unitName": "MegaPascals"
},
{
"dataID": 16545455,
"description": "Indicates that the unit is a multiplier.",
"descriptionID": 77984,
"displayName": "x",
"displayNameID": 78026,
"unitID": 104,
"unitName": "Multiplier"
},
{
"dataID": 16545456,
"description": "",
"descriptionID": null,
"displayName": "%",
"displayNameID": 78027,
"unitID": 105,
"unitName": "Percentage"
},
{
"dataID": 16545457,
"description": "",
"descriptionID": null,
"displayName": "tf",
"displayNameID": 78028,
"unitID": 106,
"unitName": "Teraflops"
},
{
"dataID": 16545458,
"description": "",
"descriptionID": null,
"displayName": "MW",
"displayNameID": 78029,
"unitID": 107,
"unitName": "MegaWatts"
},
{
"dataID": 16545459,
"description": "Used for resistance.\r\n0.0 = 100% 1.0 = 0%\r\n",
"descriptionID": 77985,
"displayName": "%",
"displayNameID": 78030,
"unitID": 108,
"unitName": "Inverse Absolute Percent"
},
{
"dataID": 16545460,
"description": "Used for multipliers displayed as %\r\n1.1 = +10%\r\n0.9 = -10%",
"descriptionID": 77986,
"displayName": "%",
"displayNameID": 78031,
"unitID": 109,
"unitName": "Modifier Percent"
},
{
"dataID": 16545461,
"description": "Used to modify damage resistance. Damage resistance bonus.\r\n0.1 = 90%\r\n0.9 = 10%",
"descriptionID": 77987,
"displayName": "%",
"displayNameID": 78032,
"unitID": 111,
"unitName": "Inversed Modifier Percent"
},
{
"dataID": 16545462,
"description": "Rotation speed.",
"descriptionID": 77988,
"displayName": "rad/sec",
"displayNameID": 78033,
"unitID": 112,
"unitName": "Radians/Second"
},
{
"dataID": 16545463,
"description": "",
"descriptionID": null,
"displayName": "HP",
"displayNameID": 78034,
"unitID": 113,
"unitName": "Hitpoints"
},
{
"dataID": 16545464,
"description": "Giga Joule",
"descriptionID": 77989,
"displayName": "GJ",
"displayNameID": 78035,
"unitID": 114,
"unitName": "capacitor units"
},
{
"dataID": 16545465,
"description": "",
"descriptionID": null,
"displayName": "groupID",
"displayNameID": 78036,
"unitID": 115,
"unitName": "groupID"
},
{
"dataID": 16545466,
"description": "",
"descriptionID": null,
"displayName": "typeID",
"displayNameID": 78037,
"unitID": 116,
"unitName": "typeID"
},
{
"dataID": 100671817,
"description": "1=small 2=medium 3=large 4=x-large",
"descriptionID": 318074,
"displayName": "1=small 2=medium 3=l",
"displayNameID": 78038,
"unitID": 117,
"unitName": "Sizeclass"
},
{
"dataID": 16545468,
"description": "",
"descriptionID": null,
"displayName": "Ore units",
"displayNameID": 78039,
"unitID": 118,
"unitName": "Ore units"
},
{
"dataID": 16545469,
"description": "",
"descriptionID": null,
"displayName": "attributeID",
"displayNameID": 78040,
"unitID": 119,
"unitName": "attributeID"
},
{
"dataID": 16545470,
"description": "",
"descriptionID": null,
"displayName": "points",
"displayNameID": 78041,
"unitID": 120,
"unitName": "attributePoints"
},
{
"dataID": 16545471,
"description": "Used for real percentages, i.e. the number 5 is 5%",
"descriptionID": 77990,
"displayName": "%",
"displayNameID": 78042,
"unitID": 121,
"unitName": "realPercent"
},
{
"dataID": 13353933,
"description": "",
"descriptionID": null,
"displayName": "",
"displayNameID": null,
"unitID": 122,
"unitName": "Fitting slots"
},
{
"dataID": 16545472,
"description": "Shows seconds directly",
"descriptionID": 77991,
"displayName": "sec",
"displayNameID": 78043,
"unitID": 123,
"unitName": "trueTime"
},
{
"dataID": 16545473,
"description": "Used for relative percentages displayed as %",
"descriptionID": 77992,
"displayName": "%",
"displayNameID": 78044,
"unitID": 124,
"unitName": "Modifier Relative Percent"
},
{
"dataID": 16545474,
"description": "",
"descriptionID": null,
"displayName": "N",
"displayNameID": 78045,
"unitID": 125,
"unitName": "Newton"
},
{
"dataID": 16545475,
"description": "",
"descriptionID": null,
"displayName": "ly",
"displayNameID": 78046,
"unitID": 126,
"unitName": "Light Year"
},
{
"dataID": 16545476,
"description": "0.0 = 0% 1.0 = 100%",
"descriptionID": 77993,
"displayName": "%",
"displayNameID": 78047,
"unitID": 127,
"unitName": "Absolute Percent"
},
{
"dataID": 16545477,
"description": "Mega bits per second",
"descriptionID": 77994,
"displayName": "Mbit/sec",
"displayNameID": 78048,
"unitID": 128,
"unitName": "Drone bandwidth"
},
{
"dataID": 16545488,
"description": "Hours",
"descriptionID": 77995,
"displayName": "",
"displayNameID": null,
"unitID": 129,
"unitName": "Hours"
},
{
"dataID": 16545478,
"description": "ISK",
"descriptionID": 77996,
"displayName": "ISK",
"displayNameID": 78049,
"unitID": 133,
"unitName": "Money"
},
{
"dataID": 16545479,
"description": "Bandwidth for PI",
"descriptionID": 77997,
"displayName": "m3/hour",
"displayNameID": 78050,
"unitID": 134,
"unitName": "Logistical Capacity"
},
{
"dataID": 16545480,
"description": "Used to denote distance, 1AU = The distance from the Earth to the Sun.",
"descriptionID": 77998,
"displayName": "AU",
"displayNameID": 78051,
"unitID": 135,
"unitName": "Astronomical Unit"
},
{
"dataID": 16545481,
"description": "Slot number prefix for various purposes",
"descriptionID": 77999,
"displayName": "Slot",
"displayNameID": 78052,
"unitID": 136,
"unitName": "Slot"
},
{
"dataID": 16545482,
"description": "For displaying boolean flags 1=True 0=False",
"descriptionID": 78000,
"displayName": "1=True 0=False",
"displayNameID": 78053,
"unitID": 137,
"unitName": "Boolean"
},
{
"dataID": 16545483,
"description": "Units of something, for example fuel",
"descriptionID": 78001,
"displayName": "units",
"displayNameID": 78054,
"unitID": 138,
"unitName": "Units"
},
{
"dataID": 16545484,
"description": "Forces a plus sign for positive values",
"descriptionID": 78002,
"displayName": "+",
"displayNameID": 78055,
"unitID": 139,
"unitName": "Bonus"
},
{
"dataID": 16545485,
"description": "For anything which is divided by levels",
"descriptionID": 78003,
"displayName": "Level",
"displayNameID": 78056,
"unitID": 140,
"unitName": "Level"
},
{
"dataID": 16545486,
"description": "For various counts to do with turret, launcher and rig hardpoints",
"descriptionID": 78004,
"displayName": "hardpoints",
"displayNameID": 78057,
"unitID": 141,
"unitName": "Hardpoints"
},
{
"dataID": 16545487,
"description": "",
"descriptionID": null,
"displayName": "1=Male 2=Unisex 3=Female",
"displayNameID": 78058,
"unitID": 142,
"unitName": "Sex"
},
{
"dataID": 97574714,
"description": "Date and time",
"descriptionID": 312106,
"displayName": "",
"displayNameID": null,
"unitID": 143,
"unitName": "Datetime"
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,368 @@
{
"1": {
"description": "Meter",
"descriptionID": 77965,
"displayName": "m",
"displayNameID": 78005,
"name": "Length"
},
"2": {
"description": "Kilogram",
"descriptionID": 77966,
"displayName": "kg",
"displayNameID": 78006,
"name": "Mass"
},
"3": {
"description": "Second",
"descriptionID": 77967,
"displayName": "sec",
"displayNameID": 78007,
"name": "Time"
},
"4": {
"description": "Ampere",
"descriptionID": 77968,
"displayName": "A",
"displayNameID": 78008,
"name": "Electric Current"
},
"5": {
"description": "Kelvin",
"descriptionID": 77969,
"displayName": "K",
"displayNameID": 78009,
"name": "Temperature"
},
"6": {
"description": "Mole",
"descriptionID": 77970,
"displayName": "mol",
"displayNameID": 78010,
"name": "Amount Of Substance"
},
"7": {
"description": "Candela",
"descriptionID": 77971,
"displayName": "cd",
"displayNameID": 78011,
"name": "Luminous Intensity"
},
"8": {
"description": "Square meter",
"descriptionID": 77972,
"displayName": "m2",
"displayNameID": 78012,
"name": "Area"
},
"9": {
"description": "Cubic meter",
"descriptionID": 77973,
"displayName": "m3",
"displayNameID": 78013,
"name": "Volume"
},
"10": {
"description": "Meter per second",
"descriptionID": 77974,
"displayName": "m/sec",
"displayNameID": 78014,
"name": "Speed"
},
"11": {
"description": "Meter per second squared",
"descriptionID": 77975,
"displayName": "m/sec",
"displayNameID": 78015,
"name": "Acceleration"
},
"12": {
"description": "Reciprocal meter",
"descriptionID": 77976,
"displayName": "m-1",
"displayNameID": 78016,
"name": "Wave Number"
},
"13": {
"description": "Kilogram per cubic meter",
"descriptionID": 77977,
"displayName": "kg/m3",
"displayNameID": 78017,
"name": "Mass Density"
},
"14": {
"description": "Cubic meter per kilogram",
"descriptionID": 77978,
"displayName": "m3/kg",
"displayNameID": 78018,
"name": "Specific Volume"
},
"15": {
"description": "Ampere per square meter",
"descriptionID": 77979,
"displayName": "A/m2",
"displayNameID": 78019,
"name": "Current Density"
},
"16": {
"description": "Ampere per meter",
"descriptionID": 77980,
"displayName": "A/m",
"displayNameID": 78020,
"name": "Magnetic Field Strength"
},
"17": {
"description": "Mole per cubic meter",
"descriptionID": 77981,
"displayName": "mol/m3",
"displayNameID": 78021,
"name": "Amount-Of-Substance Concentration"
},
"18": {
"description": "Candela per square meter",
"descriptionID": 77982,
"displayName": "cd/m2",
"displayNameID": 78022,
"name": "Luminance"
},
"19": {
"description": "Kilogram per kilogram, which may be represented by the number 1",
"descriptionID": 77983,
"displayName": "kg/kg = 1",
"displayNameID": 78023,
"name": "Mass Fraction"
},
"101": {
"displayName": "s",
"displayNameID": 78024,
"name": "Milliseconds"
},
"102": {
"displayName": "mm",
"displayNameID": 78025,
"name": "Millimeters"
},
"103": {
"name": "MegaPascals"
},
"104": {
"description": "Indicates that the unit is a multiplier.",
"descriptionID": 77984,
"displayName": "x",
"displayNameID": 78026,
"name": "Multiplier"
},
"105": {
"displayName": "%",
"displayNameID": 78027,
"name": "Percentage"
},
"106": {
"displayName": "tf",
"displayNameID": 78028,
"name": "Teraflops"
},
"107": {
"displayName": "MW",
"displayNameID": 78029,
"name": "MegaWatts"
},
"108": {
"description": "Used for resistance.\r\n0.0 = 100% 1.0 = 0%\r\n",
"descriptionID": 77985,
"displayName": "%",
"displayNameID": 78030,
"name": "Inverse Absolute Percent"
},
"109": {
"description": "Used for multipliers displayed as %\r\n1.1 = +10%\r\n0.9 = -10%",
"descriptionID": 77986,
"displayName": "%",
"displayNameID": 78031,
"name": "Modifier Percent"
},
"111": {
"description": "Used to modify damage resistance. Damage resistance bonus.\r\n0.1 = 90%\r\n0.9 = 10%",
"descriptionID": 77987,
"displayName": "%",
"displayNameID": 78032,
"name": "Inversed Modifier Percent"
},
"112": {
"description": "Rotation speed.",
"descriptionID": 77988,
"displayName": "rad/sec",
"displayNameID": 78033,
"name": "Radians/Second"
},
"113": {
"displayName": "HP",
"displayNameID": 78034,
"name": "Hitpoints"
},
"114": {
"description": "Giga Joule",
"descriptionID": 77989,
"displayName": "GJ",
"displayNameID": 78035,
"name": "capacitor units"
},
"115": {
"displayName": "groupID",
"displayNameID": 78036,
"name": "groupID"
},
"116": {
"displayName": "typeID",
"displayNameID": 78037,
"name": "typeID"
},
"117": {
"description": "1=small 2=medium 3=large 4=x-large",
"descriptionID": 318074,
"displayName": "",
"displayNameID": 78038,
"name": "Sizeclass"
},
"118": {
"displayName": "Ore units",
"displayNameID": 78039,
"name": "Ore units"
},
"119": {
"displayName": "attributeID",
"displayNameID": 78040,
"name": "attributeID"
},
"120": {
"displayName": "points",
"displayNameID": 78041,
"name": "attributePoints"
},
"121": {
"description": "Used for real percentages, i.e. the number 5 is 5%",
"descriptionID": 77990,
"displayName": "%",
"displayNameID": 78042,
"name": "realPercent"
},
"122": {
"name": "Fitting slots"
},
"123": {
"description": "Shows seconds directly",
"descriptionID": 77991,
"displayName": "sec",
"displayNameID": 78043,
"name": "trueTime"
},
"124": {
"description": "Used for relative percentages displayed as %",
"descriptionID": 77992,
"displayName": "%",
"displayNameID": 78044,
"name": "Modifier Relative Percent"
},
"125": {
"displayName": "N",
"displayNameID": 78045,
"name": "Newton"
},
"126": {
"displayName": "ly",
"displayNameID": 78046,
"name": "Light Year"
},
"127": {
"description": "0.0 = 0% 1.0 = 100%",
"descriptionID": 77993,
"displayName": "%",
"displayNameID": 78047,
"name": "Absolute Percent"
},
"128": {
"description": "Mega bits per second",
"descriptionID": 77994,
"displayName": "Mbit/sec",
"displayNameID": 78048,
"name": "Drone bandwidth"
},
"129": {
"description": "Hours",
"descriptionID": 77995,
"name": "Hours"
},
"133": {
"description": "ISK",
"descriptionID": 77996,
"displayName": "ISK",
"displayNameID": 78049,
"name": "Money"
},
"134": {
"description": "Bandwidth for PI",
"descriptionID": 77997,
"displayName": "m3/hour",
"displayNameID": 78050,
"name": "Logistical Capacity"
},
"135": {
"description": "Used to denote distance, 1AU = The distance from the Earth to the Sun.",
"descriptionID": 77998,
"displayName": "AU",
"displayNameID": 78051,
"name": "Astronomical Unit"
},
"136": {
"description": "Slot number prefix for various purposes",
"descriptionID": 77999,
"displayName": "Slot",
"displayNameID": 78052,
"name": "Slot"
},
"137": {
"description": "For displaying boolean flags 1=True 0=False",
"descriptionID": 78000,
"displayName": "",
"displayNameID": 78053,
"name": "Boolean"
},
"138": {
"description": "Units of something, for example fuel",
"descriptionID": 78001,
"displayName": "units",
"displayNameID": 78054,
"name": "Units"
},
"139": {
"description": "Forces a plus sign for positive values",
"descriptionID": 78002,
"displayName": "+",
"displayNameID": 78055,
"name": "Bonus"
},
"140": {
"description": "For anything which is divided by levels",
"descriptionID": 78003,
"displayName": "Level",
"displayNameID": 78056,
"name": "Level"
},
"141": {
"description": "For various counts to do with turret, launcher and rig hardpoints",
"descriptionID": 78004,
"displayName": "hardpoints",
"displayNameID": 78057,
"name": "Hardpoints"
},
"142": {
"displayName": "1=Male 2=Unisex 3=Female",
"displayNameID": 78058,
"name": "Sex"
},
"143": {
"description": "Date and time",
"descriptionID": 312106,
"name": "Datetime"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
{
"1": {
"name": "Tech I",
"nameID": 66672
},
"2": {
"iconID": 24150,
"iconSuffix": "t2",
"name": "Tech II",
"nameID": 66673
},
"3": {
"iconID": 24147,
"iconSuffix": "storyline",
"name": "Storyline",
"nameID": 66674
},
"4": {
"iconID": 24146,
"iconSuffix": "faction",
"name": "Faction",
"nameID": 66675
},
"5": {
"iconID": 24149,
"iconSuffix": "officer",
"name": "Officer",
"nameID": 66676
},
"6": {
"description": "Modules found in deadspace.",
"descriptionID": 66671,
"iconID": 24148,
"iconSuffix": "deadspace",
"name": "Deadspace",
"nameID": 66677
},
"7": {
"name": "Frigates",
"nameID": 66678
},
"8": {
"name": "Elite Frigates",
"nameID": 66679
},
"9": {
"name": "Commander Frigates",
"nameID": 66680
},
"10": {
"name": "Destroyer",
"nameID": 66681
},
"11": {
"name": "Cruiser",
"nameID": 66682
},
"12": {
"name": "Elite Cruiser",
"nameID": 66683
},
"13": {
"name": "Commander Cruiser",
"nameID": 66684
},
"14": {
"iconID": 24151,
"iconSuffix": "t3",
"name": "Tech III",
"nameID": 66685
},
"15": {
"iconID": 24152,
"iconSuffix": "abyssal",
"name": "Abyssal",
"nameID": 317771
},
"17": {
"description": "This item is only available through the New Eden Store or exclusive offers.",
"descriptionID": 317894,
"iconID": 24153,
"iconSuffix": "premium",
"name": "Premium",
"nameID": 317893
},
"19": {
"description": "This item is only available for a limited time.",
"descriptionID": 317896,
"iconID": 24154,
"iconSuffix": "limited",
"name": "Limited Time",
"nameID": 317895
},
"52": {
"iconID": 24155,
"iconSuffix": "struct_faction",
"name": "Structure Faction",
"nameID": 550638
},
"53": {
"iconID": 24156,
"iconSuffix": "struct_t2",
"name": "Structure Tech II",
"nameID": 550639
},
"54": {
"iconID": 24157,
"iconSuffix": "struct",
"name": "Structure Tech I",
"nameID": 550644
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
{
"0": {
"categoryID": 0,
"categoryName": "#System",
"categoryNameID": 63539,
"published": false
},
"1": {
"categoryID": 1,
"categoryName": "Owner",
"categoryNameID": 63540,
"published": false
},
"10": {
"categoryID": 10,
"categoryName": "Trading",
"categoryNameID": 63548,
"published": false
},
"11": {
"categoryID": 11,
"categoryName": "Entity",
"categoryNameID": 63549,
"published": false
},
"14": {
"categoryID": 14,
"categoryName": "Bonus",
"categoryNameID": 63550,
"iconID": 0,
"published": false
},
"16": {
"categoryID": 16,
"categoryName": "Skill",
"categoryNameID": 63551,
"iconID": 33,
"published": true
},
"17": {
"categoryID": 17,
"categoryName": "Commodity",
"categoryNameID": 63552,
"iconID": 0,
"published": true
},
"18": {
"categoryID": 18,
"categoryName": "Drone",
"categoryNameID": 63553,
"iconID": 0,
"published": true,
"sofBuildClass": "ship"
},
"2": {
"categoryID": 2,
"categoryName": "Celestial",
"categoryNameID": 63541,
"published": true
},
"20": {
"categoryID": 20,
"categoryName": "Implant",
"categoryNameID": 63554,
"iconID": 0,
"published": true
},
"22": {
"categoryID": 22,
"categoryName": "Deployable",
"categoryNameID": 63555,
"iconID": 0,
"published": true
},
"23": {
"categoryID": 23,
"categoryName": "Starbase",
"categoryNameID": 63556,
"iconID": 0,
"published": true
},
"24": {
"categoryID": 24,
"categoryName": "Reaction",
"categoryNameID": 63557,
"iconID": 0,
"published": true
},
"25": {
"categoryID": 25,
"categoryName": "Asteroid",
"categoryNameID": 63558,
"published": true
},
"26": {
"categoryID": 26,
"categoryName": "WorldSpace",
"categoryNameID": 63568,
"published": false
},
"29": {
"categoryID": 29,
"categoryName": "Abstract",
"categoryNameID": 63559,
"published": false
},
"3": {
"categoryID": 3,
"categoryName": "Station",
"categoryNameID": 63542,
"published": false
},
"30": {
"categoryID": 30,
"categoryName": "Apparel",
"categoryNameID": 63572,
"published": true
},
"32": {
"categoryID": 32,
"categoryName": "Subsystem",
"categoryNameID": 63562,
"published": true
},
"34": {
"categoryID": 34,
"categoryName": "Ancient Relics",
"categoryNameID": 63561,
"published": true
},
"35": {
"categoryID": 35,
"categoryName": "Decryptors",
"categoryNameID": 63563,
"published": true
},
"350001": {
"categoryID": 350001,
"categoryName": "Infantry",
"categoryNameID": 267649,
"published": false
},
"39": {
"categoryID": 39,
"categoryName": "Infrastructure Upgrades",
"categoryNameID": 63565,
"published": true
},
"4": {
"categoryID": 4,
"categoryName": "Material",
"categoryNameID": 63543,
"iconID": 22,
"published": true
},
"40": {
"categoryID": 40,
"categoryName": "Sovereignty Structures",
"categoryNameID": 63564,
"published": true
},
"41": {
"categoryID": 41,
"categoryName": "Planetary Interaction",
"categoryNameID": 63569,
"published": true
},
"42": {
"categoryID": 42,
"categoryName": "Planetary Resources",
"categoryNameID": 63566,
"published": true
},
"43": {
"categoryID": 43,
"categoryName": "Planetary Commodities",
"categoryNameID": 63567,
"published": true
},
"46": {
"categoryID": 46,
"categoryName": "Orbitals",
"categoryNameID": 63570,
"published": true
},
"49": {
"categoryID": 49,
"categoryName": "Placeables",
"categoryNameID": 63571,
"published": false
},
"5": {
"categoryID": 5,
"categoryName": "Accessories",
"categoryNameID": 63560,
"iconID": 33,
"published": true
},
"53": {
"categoryID": 53,
"categoryName": "Effects",
"categoryNameID": 63573,
"published": false
},
"54": {
"categoryID": 54,
"categoryName": "Lights",
"categoryNameID": 63574,
"published": false
},
"59": {
"categoryID": 59,
"categoryName": "Cells",
"categoryNameID": 235965,
"published": false
},
"6": {
"categoryID": 6,
"categoryName": "Ship",
"categoryNameID": 63544,
"published": true
},
"63": {
"categoryID": 63,
"categoryName": "Special Edition Assets",
"categoryNameID": 285070,
"published": true
},
"65": {
"categoryID": 65,
"categoryName": "Structure",
"categoryNameID": 308338,
"published": true
},
"66": {
"categoryID": 66,
"categoryName": "Structure Module",
"categoryNameID": 308340,
"published": true
},
"7": {
"categoryID": 7,
"categoryName": "Module",
"categoryNameID": 63545,
"iconID": 67,
"published": true
},
"8": {
"categoryID": 8,
"categoryName": "Charge",
"categoryNameID": 63546,
"published": true
},
"87": {
"categoryID": 87,
"categoryName": "Fighter",
"categoryNameID": 510368,
"published": true
},
"9": {
"categoryID": 9,
"categoryName": "Blueprint",
"categoryNameID": 63547,
"iconID": 21,
"published": true
},
"91": {
"categoryID": 91,
"categoryName": "SKINs",
"categoryNameID": 531338,
"published": true
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
[
{
"field_name": "client_build",
"field_value": 1610407
},
{
"field_name": "dump_time",
"field_value": 1574329773
}
]

19499
staticdata/phobos/traits.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
# Add root folder to python paths
# This must be done on every test in order to pass in Travis
import os
import sys
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..', '..')))
import pytest
from eos.utils.stats import DmgTypes, RRTypes
@pytest.fixture()
def setup_damage_types():
return DmgTypes(10, 20, 30, 40)
def test_dmgtypes_names():
assert DmgTypes.names() == ['em', 'thermal', 'kinetic', 'explosive']
assert DmgTypes.names(True) == ['em', 'th', 'kin', 'exp']
assert DmgTypes.names(short=True) == ['em', 'th', 'kin', 'exp']
def test_dmgtypes__repr(setup_damage_types):
assert setup_damage_types.__repr__() == '<DmgTypes(em=10, thermal=20, kinetic=30, explosive=40, total=100)>'
def test_dmgtypes_names_lambda():
assert DmgTypes.names(False, lambda v: v.capitalize()) == ['Em', 'Thermal', 'Kinetic', 'Explosive']
assert DmgTypes.names(True, lambda v: v.upper()) == ['EM', 'TH', 'KIN', 'EXP']
@pytest.fixture()
def setup_rr_types():
return RRTypes(10, 20, 30, 40)
def test_rrtypes_names():
assert RRTypes.names() == ['shield', 'armor', 'hull']
assert RRTypes.names(True) == ['shield', 'armor', 'hull']
assert RRTypes.names(ehpOnly=True) == ['shield', 'armor', 'hull']
assert RRTypes.names(False) == ['shield', 'armor', 'hull', 'capacitor']
def test_rrtypes__repr(setup_rr_types):
assert setup_rr_types.__repr__() == '<RRTypes(shield=10, armor=20, hull=30, capacitor=40)>'
def test_rrtypes_names_lambda():
assert RRTypes.names(True, lambda v: v.capitalize()) == ['Shield', 'Armor', 'Hull']
assert RRTypes.names(postProcessor=lambda v: v.upper(), ehpOnly=False) == ['SHIELD', 'ARMOR', 'HULL', 'CAPACITOR']

View File

@@ -5,7 +5,8 @@ import sys
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..')))
# noinspection PyPackageRequirements
# This import is here to hack around circular import issues
import gui.mainFrame
# noinspection PyPackageRequirements
from service.fit import Fit

View File

@@ -9,17 +9,15 @@ import os
import sys
# nopep8
import re
# from utils.strfunctions import sequential_rep, replace_ltgt
#from utils.stopwatch import Stopwatch
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.realpath(os.path.join(script_dir, '..')))
sys._called_from_test = True # need db open for tests. (see eos/config.py#17
# This import is here to hack around circular import issues
import gui.mainFrame
# noinspection PyPep8
from service.port import Port, IPortUser
#
# noinspection PyPackageRequirements
# from _development.helpers import DBInMemory as DB
"""
NOTE:

View File

@@ -1 +1 @@
version: v2.13.0
version: v2.14.3