Compare commits
126 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3e94729c | ||
|
|
fb48f2b5d4 | ||
|
|
cfffc77777 | ||
|
|
f7089f358d | ||
|
|
06c4f2ce46 | ||
|
|
83eb0abd92 | ||
|
|
4199b33c47 | ||
|
|
23cd4bff5a | ||
|
|
b65f95fe77 | ||
|
|
32160c94e1 | ||
|
|
ac02fba98b | ||
|
|
cde0108cba | ||
|
|
39dc7e4a46 | ||
|
|
9943f784a8 | ||
|
|
88ce45f29e | ||
|
|
7157e876ca | ||
|
|
0cf88cf7ca | ||
|
|
10dfdc3627 | ||
|
|
76bdefcda6 | ||
|
|
1c2c8cc5f9 | ||
|
|
58f853de5b | ||
|
|
c052297bf7 | ||
|
|
9e78cd1076 | ||
|
|
79f4deacea | ||
|
|
ff42c4c711 | ||
|
|
02d31d49d8 | ||
|
|
64f47fcc24 | ||
|
|
0ceb8acd64 | ||
|
|
78579e2e13 | ||
|
|
cf4e1d3935 | ||
|
|
d1be0bb680 | ||
|
|
384d9f4614 | ||
|
|
47434c68f9 | ||
|
|
af88afb6b5 | ||
|
|
536eb1efa5 | ||
|
|
c4c763089e | ||
|
|
cdfd4c0d8e | ||
|
|
f9bb8836e5 | ||
|
|
58b2634c8c | ||
|
|
093ae008ce | ||
|
|
5f62fc0cdc | ||
|
|
e7a4b4ac26 | ||
|
|
66e9944cb5 | ||
|
|
ad8528c248 | ||
|
|
07d22cd8e4 | ||
|
|
a6d5922d77 | ||
|
|
958d7bff99 | ||
|
|
7819b80be4 | ||
|
|
2ca50a4658 | ||
|
|
09ff4fd128 | ||
|
|
3e53863f9e | ||
|
|
63d2289f97 | ||
|
|
2663ef2e66 | ||
|
|
d4bdf47d62 | ||
|
|
660ee7c4bf | ||
|
|
1db1f3070b | ||
|
|
3dba82c497 | ||
|
|
25e7b7a9f7 | ||
|
|
9582212ae0 | ||
|
|
7d2b60c327 | ||
|
|
0121a0064e | ||
|
|
2aa96fc819 | ||
|
|
8d81db0a3a | ||
|
|
e5ba35fde9 | ||
|
|
885cd32cb0 | ||
|
|
18d8ed6558 | ||
|
|
9618ece4b4 | ||
|
|
a80a77a422 | ||
|
|
3806be3ddd | ||
|
|
3e803fef30 | ||
|
|
12956d435a | ||
|
|
a3381007f3 | ||
|
|
1efe4ee5e5 | ||
|
|
ec21f93d3c | ||
|
|
f384b32ed6 | ||
|
|
22d8f34c75 | ||
|
|
6128cd8322 | ||
|
|
386f403430 | ||
|
|
5f7d9aea89 | ||
|
|
b367c449a9 | ||
|
|
26b3dff9d4 | ||
|
|
873a62e3f0 | ||
|
|
d967ab375e | ||
|
|
fcf2d6a72c | ||
|
|
843ced15bf | ||
|
|
813db9340f | ||
|
|
acbd8a3298 | ||
|
|
561e22e894 | ||
|
|
05ac0a528a | ||
|
|
c040353f6e | ||
|
|
f23a8fa0c8 | ||
|
|
ba93467646 | ||
|
|
00d480860f | ||
|
|
c94384acb8 | ||
|
|
0c2c0ac6ef | ||
|
|
61a33a331e | ||
|
|
e374a6f2c6 | ||
|
|
dbd84dce28 | ||
|
|
9d554f9c68 | ||
|
|
576cf56735 | ||
|
|
e2aaabbc16 | ||
|
|
ef226898c0 | ||
|
|
a0db235e5a | ||
|
|
5bf05ba775 | ||
|
|
c073b1fa2a | ||
|
|
5f58307bf3 | ||
|
|
8741b17a5e | ||
|
|
4c1fa09795 | ||
|
|
ce7df2d01f | ||
|
|
b433b0ea7c | ||
|
|
20868d6b44 | ||
|
|
33103dbee9 | ||
|
|
2a05ac5a85 | ||
|
|
a013828128 | ||
|
|
e19510b3d4 | ||
|
|
390f2048f2 | ||
|
|
0bb732300e | ||
|
|
fd017df561 | ||
|
|
0ed16b9a6f | ||
|
|
865978fcc1 | ||
|
|
a43f9930de | ||
|
|
c13cd23d54 | ||
|
|
ed1f52a114 | ||
|
|
7dd063f04e | ||
|
|
6e9fc1d1d9 | ||
|
|
b2c718d614 |
@@ -52,8 +52,8 @@ install:
|
||||
# pip will build them from source using the MSVC compiler matching the
|
||||
# target Python version and architecture
|
||||
- ECHO "Install pip requirements:"
|
||||
- "pip install -r requirements.txt"
|
||||
- "pip install PyInstaller"
|
||||
- "python -m pip install -r requirements.txt"
|
||||
- "python -m pip install PyInstaller"
|
||||
|
||||
before_build:
|
||||
# directory that will contain the built files
|
||||
@@ -65,6 +65,8 @@ before_build:
|
||||
build_script:
|
||||
- ECHO "Build pyfa:"
|
||||
|
||||
# Build gamedata DB
|
||||
- "python db_update.py"
|
||||
##########
|
||||
# PyInstaller - create binaries for pyfa
|
||||
##########
|
||||
|
||||
@@ -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:
|
||||
|
||||
79
CONTRIBUTING.md
Normal file
79
CONTRIBUTING.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Contribution
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.6
|
||||
- Git CLI installed
|
||||
- Python, pip and git are all available as command-line commands (add to 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 virtual environment
|
||||
```
|
||||
python -m venv PyfaEnv
|
||||
```
|
||||
|
||||
Activate virtual environment
|
||||
|
||||
```
|
||||
For cmd.exe: PyfaEnv\scripts\activate.bat
|
||||
For bash: source <venv>/bin/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*
|
||||
|
||||

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

|
||||
|
||||
After process is complete, open *File* -> *Settings* -> *Project* -> *Project Interpreter*.
|
||||
|
||||

|
||||
|
||||
Press on options and add new virtual environment.
|
||||
|
||||

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

|
||||
|
||||
Create new *Run Configuration*. Set correct *Script path* and *Python interpreter*.
|
||||
|
||||

|
||||
|
||||
Test that the project is starting properly.
|
||||
34
README.md
34
README.md
@@ -2,20 +2,19 @@
|
||||
|
||||
[](https://pyfainvite.azurewebsites.net/) [](https://travis-ci.org/pyfa-org/Pyfa)
|
||||
|
||||

|
||||

|
||||
|
||||
## What is it?
|
||||
|
||||
pyfa, short for **py**thon **f**itting **a**ssistant, allows you to create, experiment with, and save ship fittings without being in game. Open source and written in Python, it is available on any platform where Python 2.x and wxWidgets are available, including Windows, Mac OS X, and Linux.
|
||||
Pyfa, short for **py**thon **f**itting **a**ssistant, allows you to create, experiment with, and save ship fittings without being in game. Open source and written in Python, it is available on any platform where Python 3 and wxWidgets are available, including Windows, Mac OS X, and Linux.
|
||||
|
||||
## Latest Version and Changelogs
|
||||
The latest version along with release notes can always be found on the project's [Releases](https://github.com/DarkFenX/Pyfa/releases) page. pyfa will notify you if you are running an outdated version.
|
||||
The latest version along with release notes can always be found on the project's [releases](https://github.com/pyfa-org/Pyfa/releases) page. Pyfa will notify you if you are running an outdated version.
|
||||
|
||||
## 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,34 +26,31 @@ 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](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/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).
|
||||
|
||||
## License
|
||||
pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
||||
Pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
||||
|
||||
## Resources
|
||||
* Development repository: [https://github.com/pyfa-org/Pyfa](https://github.com/pyfa-org/Pyfa)
|
||||
* [Development repository](https://github.com/pyfa-org/Pyfa)
|
||||
* [EVE forum thread](https://forums.eveonline.com/t/27156)
|
||||
* [EVE University guide using pyfa](http://wiki.eveuniversity.org/Guide_to_using_PYFA)
|
||||
* [EVE University guide using pyfa](https://wiki.eveuniversity.org/PYFA)
|
||||
* [EVE Online website](http://www.eveonline.com/)
|
||||
|
||||
## Contacts:
|
||||
* Sable Blitzmann
|
||||
* GitHub: @blitzmann
|
||||
* [TweetFleet Slack](https://www.fuzzwork.co.uk/tweetfleet-slack-invites/): @blitzmann
|
||||
* [Gitter chat](https://gitter.im/pyfa-org/Pyfa): @ blitzmann
|
||||
* Email: sable.blitzmann@gmail.com
|
||||
* Kadesh / DarkPhoenix
|
||||
* GitHub: @DarkFenX
|
||||
* EVE: Kadesh Priestess
|
||||
* Email: phoenix@mail.ru
|
||||
* Sable Blitzmann
|
||||
* GitHub: @blitzmann
|
||||
* [TweetFleet Slack](https://www.fuzzwork.co.uk/tweetfleet-slack-invites/): @blitzmann
|
||||
* [Gitter chat](https://gitter.im/pyfa-org/Pyfa): @blitzmann
|
||||
* Email: sable.blitzmann@gmail.com
|
||||
|
||||
## CCP Copyright Notice
|
||||
EVE Online, the EVE logo, EVE and all associated logos and designs are the intellectual property of CCP hf. All artwork, screenshots, characters, vehicles, storylines, world facts or other recognizable features of the intellectual property relating to these trademarks are likewise the intellectual property of CCP hf. EVE Online and the EVE logo are the registered trademarks of CCP hf. All rights are reserved worldwide. All other trademarks are the property of their respective owners. CCP hf. has granted permission to pyfa to use EVE Online and all associated logos and designs for promotional and information purposes on its website but does not endorse, and is not in any way affiliated with, pyfa. CCP is in no way responsible for the content on or functioning of this program, nor can it be liable for any damage arising from the use of this program.
|
||||
|
||||
12
config.py
12
config.py
@@ -33,11 +33,13 @@ pyfaPath = None
|
||||
savePath = None
|
||||
saveDB = None
|
||||
gameDB = None
|
||||
imgsZIP = None
|
||||
logPath = None
|
||||
loggingLevel = None
|
||||
logging_setup = None
|
||||
cipher = None
|
||||
clientHash = None
|
||||
experimentalFeatures = None
|
||||
|
||||
ESI_CACHE = 'esi_cache'
|
||||
|
||||
@@ -96,11 +98,13 @@ def defPaths(customSavePath=None):
|
||||
global savePath
|
||||
global saveDB
|
||||
global gameDB
|
||||
global imgsZIP
|
||||
global saveInRoot
|
||||
global logPath
|
||||
global cipher
|
||||
global clientHash
|
||||
global version
|
||||
global experimentalFeatures
|
||||
|
||||
pyfalog.debug("Configuring Pyfa")
|
||||
|
||||
@@ -155,6 +159,10 @@ def defPaths(customSavePath=None):
|
||||
if not gameDB:
|
||||
gameDB = os.path.join(pyfaPath, "eve.db")
|
||||
|
||||
imgsZIP = getattr(configforced, "imgsZIP", imgsZIP)
|
||||
if not imgsZIP:
|
||||
imgsZIP = os.path.join(pyfaPath, "imgs.zip")
|
||||
|
||||
if debug:
|
||||
logFile = "pyfa_debug.log"
|
||||
else:
|
||||
@@ -162,6 +170,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
|
||||
|
||||
|
||||
142
scripts/jsonToSql.py → db_update.py
Executable file → Normal file
142
scripts/jsonToSql.py → db_update.py
Executable file → Normal 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
|
||||
|
||||
@@ -56,15 +91,14 @@ def main(db, json_path):
|
||||
# Config dict
|
||||
tables = {
|
||||
'clonegrades': ('fsd_lite', eos.gamedata.AlphaCloneSkill),
|
||||
'dgmattribs': ('bulkdata', eos.gamedata.AttributeInfo),
|
||||
'dgmeffects': ('bulkdata', eos.gamedata.Effect),
|
||||
'dgmtypeattribs': ('bulkdata', eos.gamedata.Attribute),
|
||||
'dgmtypeeffects': ('bulkdata', eos.gamedata.ItemEffect),
|
||||
'dgmunits': ('bulkdata', eos.gamedata.Unit),
|
||||
'dogmaattributes': ('bulkdata', eos.gamedata.AttributeInfo),
|
||||
'dogmaeffects': ('bulkdata', eos.gamedata.Effect),
|
||||
'dogmatypeattributes': ('bulkdata', eos.gamedata.Attribute),
|
||||
'dogmatypeeffects': ('bulkdata', eos.gamedata.ItemEffect),
|
||||
'dogmaunits': ('bulkdata', eos.gamedata.Unit),
|
||||
'evecategories': ('fsd_lite', eos.gamedata.Category),
|
||||
'evegroups': ('fsd_lite', eos.gamedata.Group),
|
||||
'invmetagroups': ('bulkdata', eos.gamedata.MetaGroup),
|
||||
'invmetatypes': ('bulkdata', eos.gamedata.MetaType),
|
||||
'metagroups': ('fsd_binary', eos.gamedata.MetaGroup),
|
||||
'evetypes': ('fsd_lite', eos.gamedata.Item),
|
||||
'traits': ('phobos', eos.gamedata.Traits),
|
||||
'metadata': ('phobos', eos.gamedata.MetaData),
|
||||
@@ -73,13 +107,16 @@ def main(db, json_path):
|
||||
fieldMapping = {
|
||||
'marketgroups': {
|
||||
'id': 'marketGroupID',
|
||||
'name': 'marketGroupName'}}
|
||||
'name': 'marketGroupName'},
|
||||
'metagroups': {
|
||||
'id': 'metaGroupID'}}
|
||||
|
||||
rowsInValues = (
|
||||
'evetypes',
|
||||
'evegroups',
|
||||
'evecategories',
|
||||
'marketgroups')
|
||||
'marketgroups',
|
||||
'metagroups')
|
||||
|
||||
def convertIcons(data):
|
||||
new = []
|
||||
@@ -176,13 +213,13 @@ def main(db, json_path):
|
||||
# Get data on item effects
|
||||
# Format: {type ID: set(effect, IDs)}
|
||||
typesEffects = {}
|
||||
for row in tables['dgmtypeeffects']:
|
||||
for row in tables['dogmatypeeffects']:
|
||||
typesEffects.setdefault(row['typeID'], set()).add(row['effectID'])
|
||||
# Get data on type attributes
|
||||
# Format: {type ID: {attribute ID: attribute value}}
|
||||
typesNormalAttribs = {}
|
||||
typesSkillAttribs = {}
|
||||
for row in tables['dgmtypeattribs']:
|
||||
for row in tables['dogmatypeattributes']:
|
||||
attributeID = row['attributeID']
|
||||
if attributeID in skillReqAttribsFlat:
|
||||
typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
|
||||
@@ -272,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 = []
|
||||
@@ -300,10 +337,11 @@ def main(db, json_path):
|
||||
for row in data['evetypes']:
|
||||
if (
|
||||
row['published'] or
|
||||
row['typeName'] == 'Capsule' or
|
||||
# group Ship Modifiers, for items like tactical t3 ship modes
|
||||
row['groupID'] == 1306 or
|
||||
# Civilian weapons
|
||||
row['typeName'].startswith('Civilian') or
|
||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
||||
# Micro Bombs (Fighters)
|
||||
row['typeID'] in (41549, 41548, 41551, 41550) or
|
||||
# Abyssal weather (environment)
|
||||
@@ -313,14 +351,14 @@ def main(db, json_path):
|
||||
1971,
|
||||
# the "container" for the abyssal environments
|
||||
1983) or
|
||||
# Dark Blood Tracking Disruptor (rarely but drops)
|
||||
# Dark Blood Tracking Disruptor (drops, but rarely)
|
||||
row['typeID'] == 32416
|
||||
):
|
||||
eveTypes.add(row['typeID'])
|
||||
|
||||
# ignore checker
|
||||
def isIgnored(file, row):
|
||||
if file in ('evetypes', 'dgmtypeeffects', 'dgmtypeattribs', 'invmetatypes') and row['typeID'] not in eveTypes:
|
||||
if file in ('evetypes', 'dogmatypeeffects', 'dogmatypeattributes') and row['typeID'] not in eveTypes:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -337,8 +375,8 @@ def main(db, json_path):
|
||||
if (
|
||||
jsonName == 'evetypes' and (
|
||||
# Apparently people really want Civilian modules available
|
||||
row['typeName'].startswith('Civilian') or
|
||||
row['typeName'] == 'Dark Blood Tracking Disruptor')
|
||||
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
|
||||
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor'))
|
||||
):
|
||||
row['published'] = True
|
||||
|
||||
@@ -370,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()
|
||||
@@ -392,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)
|
||||
@@ -415,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()
|
||||
71
eos/calc.py
Normal file
71
eos/calc.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2019 Ryan Holmes
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import 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:
|
||||
return 1
|
||||
if srcFalloffRange > 0:
|
||||
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
||||
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
||||
return 0
|
||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||
elif distance <= srcOptimalRange:
|
||||
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)
|
||||
@@ -25,7 +25,7 @@ from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
from eos.db import gamedata_meta
|
||||
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table
|
||||
from eos.db.gamedata.effect import typeeffects_table
|
||||
from eos.gamedata import Attribute, DynamicItem, Effect, Group, Item, MetaType, Traits
|
||||
from eos.gamedata import Attribute, DynamicItem, Effect, Group, Item, Traits, MetaGroup
|
||||
|
||||
items_table = Table("invtypes", gamedata_meta,
|
||||
Column("typeID", Integer, primary_key=True),
|
||||
@@ -41,9 +41,11 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("iconID", Integer),
|
||||
Column("graphicID", Integer),
|
||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
||||
Column("metaLevel", Integer),
|
||||
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID"), index=True),
|
||||
Column("variationParentTypeID", Integer, ForeignKey("invtypes.typeID"), index=True),
|
||||
Column("replacements", String))
|
||||
|
||||
from .metaGroup import metatypes_table # noqa
|
||||
from .traits import traits_table # noqa
|
||||
|
||||
mapper(Item, items_table,
|
||||
@@ -51,9 +53,8 @@ mapper(Item, items_table,
|
||||
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
|
||||
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
|
||||
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
|
||||
"metaGroup" : relation(MetaType,
|
||||
primaryjoin=metatypes_table.c.typeID == items_table.c.typeID,
|
||||
uselist=False),
|
||||
"metaGroup" : relation(MetaGroup, backref=backref("items", cascade="all,delete")),
|
||||
"varParent" : relation(Item, backref=backref("varChildren", cascade="all,delete"), remote_side=items_table.c.typeID),
|
||||
"ID" : synonym("typeID"),
|
||||
"name" : synonym("typeName"),
|
||||
"description" : deferred(items_table.c.description),
|
||||
@@ -64,7 +65,6 @@ mapper(Item, items_table,
|
||||
primaryjoin=dynamicApplicable_table.c.applicableTypeID == items_table.c.typeID,
|
||||
secondaryjoin=dynamicApplicable_table.c.typeID == DynamicItem.typeID,
|
||||
secondary=dynamicApplicable_table,
|
||||
backref="applicableItems")
|
||||
})
|
||||
backref="applicableItems")})
|
||||
|
||||
Item.category = association_proxy("group", "category")
|
||||
|
||||
@@ -17,35 +17,17 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, String
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm import relation, mapper, synonym
|
||||
from sqlalchemy import Table, Column, Integer, String
|
||||
from sqlalchemy.orm import mapper, synonym
|
||||
|
||||
from eos.db import gamedata_meta
|
||||
from eos.db.gamedata.item import items_table
|
||||
from eos.gamedata import Item, MetaGroup, MetaType
|
||||
from eos.gamedata import MetaGroup
|
||||
|
||||
metagroups_table = Table("invmetagroups", gamedata_meta,
|
||||
Column("metaGroupID", Integer, primary_key=True),
|
||||
Column("metaGroupName", String))
|
||||
|
||||
metatypes_table = Table("invmetatypes", gamedata_meta,
|
||||
Column("typeID", Integer, ForeignKey("invtypes.typeID"), primary_key=True),
|
||||
Column("parentTypeID", Integer, ForeignKey("invtypes.typeID")),
|
||||
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID")))
|
||||
|
||||
mapper(MetaGroup, metagroups_table,
|
||||
properties={
|
||||
"ID" : synonym("metaGroupID"),
|
||||
"name": synonym("metaGroupName")
|
||||
})
|
||||
|
||||
mapper(MetaType, metatypes_table,
|
||||
properties={
|
||||
"ID" : synonym("metaGroupID"),
|
||||
"parent": relation(Item, primaryjoin=metatypes_table.c.parentTypeID == items_table.c.typeID),
|
||||
"items" : relation(Item, primaryjoin=metatypes_table.c.typeID == items_table.c.typeID),
|
||||
"info" : relation(MetaGroup, lazy=False)
|
||||
})
|
||||
|
||||
MetaType.name = association_proxy("info", "name")
|
||||
"name": synonym("metaGroupName")})
|
||||
|
||||
@@ -23,8 +23,8 @@ from sqlalchemy.sql import and_, or_, select
|
||||
|
||||
import eos.config
|
||||
from eos.db import gamedata_session
|
||||
from eos.db.gamedata.item import items_table
|
||||
from eos.db.gamedata.group import groups_table
|
||||
from eos.db.gamedata.metaGroup import items_table, metatypes_table
|
||||
from eos.db.util import processEager, processWhere
|
||||
from eos.gamedata import AlphaClone, Attribute, AttributeInfo, Category, DynamicItem, Group, Item, MarketGroup, MetaData, MetaGroup
|
||||
|
||||
@@ -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,12 +256,17 @@ 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
|
||||
|
||||
|
||||
def getMetaGroups():
|
||||
return gamedata_session.query(MetaGroup).all()
|
||||
|
||||
|
||||
@cachedQuery(1, "lookfor")
|
||||
def getMarketGroup(lookfor, eager=None):
|
||||
if isinstance(lookfor, int):
|
||||
@@ -342,11 +350,9 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None):
|
||||
if len(itemids) == 0:
|
||||
return []
|
||||
|
||||
itemfilter = or_(*(metatypes_table.c.parentTypeID == itemid for itemid in itemids))
|
||||
itemfilter = or_(*(items_table.c.variationParentTypeID == itemid for itemid in itemids))
|
||||
filter = processWhere(itemfilter, where)
|
||||
joinon = items_table.c.typeID == metatypes_table.c.typeID
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).join((metatypes_table, joinon)).filter(
|
||||
filter).all()
|
||||
vars = gamedata_session.query(Item).options(*processEager(eager)).filter(filter).all()
|
||||
|
||||
if vars:
|
||||
return vars
|
||||
|
||||
25
eos/db/migrations/upgrade34.py
Normal file
25
eos/db/migrations/upgrade34.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Migration 34
|
||||
|
||||
- Adds projection range columns to projectable entities
|
||||
"""
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM projectedFits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM modules LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM drones LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE drones ADD COLUMN projectionRange FLOAT;")
|
||||
try:
|
||||
saveddata_engine.execute("SELECT projectionRange FROM fighters LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fighters ADD COLUMN projectionRange FLOAT;")
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy.orm import mapper, relation
|
||||
import datetime
|
||||
|
||||
@@ -33,7 +33,8 @@ drones_table = Table("drones", saveddata_meta,
|
||||
Column("amountActive", Integer, nullable=False),
|
||||
Column("projected", Boolean, default=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True)
|
||||
)
|
||||
|
||||
mapper(Drone, drones_table,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime
|
||||
from sqlalchemy.orm import mapper, relation
|
||||
import datetime
|
||||
|
||||
@@ -34,7 +34,8 @@ fighters_table = Table("fighters", saveddata_meta,
|
||||
Column("amount", Integer, nullable=False),
|
||||
Column("projected", Boolean, default=False),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
)
|
||||
|
||||
fighter_abilities_table = Table("fightersAbilities", saveddata_meta,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Table
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, Float, String, Table
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm import mapper, reconstructor, relation, relationship
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
@@ -70,7 +70,8 @@ projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
Column("amount", Integer, nullable=False, default=1),
|
||||
Column("active", Boolean, nullable=False, default=1),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
)
|
||||
|
||||
commandFits_table = Table("commandFits", saveddata_meta,
|
||||
@@ -83,6 +84,7 @@ commandFits_table = Table("commandFits", saveddata_meta,
|
||||
|
||||
|
||||
class ProjectedFit:
|
||||
|
||||
def __init__(self, sourceID, source_fit, amount=1, active=True):
|
||||
self.sourceID = sourceID
|
||||
self.source_fit = source_fit
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import eos.db
|
||||
from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern
|
||||
from eos.saveddata.targetProfile import TargetProfile as es_TargetProfile
|
||||
@@ -34,95 +35,97 @@ class DefaultDatabaseValues:
|
||||
|
||||
@classmethod
|
||||
def importDamageProfileDefaults(cls):
|
||||
damageProfileList = [["Uniform", "25", "25", "25", "25"], ["[Generic]EM", "100", "0", "0", "0"],
|
||||
["[Generic]Thermal", "0", "100", "0", "0"], ["[Generic]Kinetic", "0", "0", "100", "0"],
|
||||
["[Generic]Explosive", "0", "0", "0", "100"],
|
||||
["[NPC][Asteroid] Blood Raiders", "5067", "4214", "0", "0"],
|
||||
["[Bombs]Electron Bomb", "6400", "0", "0", "0"],
|
||||
["[Bombs]Scorch Bomb", "0", "6400", "0", "0"],
|
||||
["[Bombs]Concussion Bomb", "0", "0", "6400", "0"],
|
||||
["[Bombs]Shrapnel Bomb", "0", "0", "0", "6400"],
|
||||
["[Frequency Crystals][T2] Conflagration", "61.6", "61.6", "0", "0"],
|
||||
["[Frequency Crystals][T2] Scorch", "72", "16", "0", "0"],
|
||||
["[Frequency Crystals][T2] Gleam", "56", "56", "0", "0"],
|
||||
["[Frequency Crystals][T2] Aurora", "40", "24", "0", "0"],
|
||||
["[Frequency Crystals]Multifrequency", "61.6", "44", "0", "0"],
|
||||
["[Frequency Crystals]Gamma", "61.6", "35.2", "0", "0"],
|
||||
["[Frequency Crystals]Xray", "52.8", "35.2", "0", "0"],
|
||||
["[Frequency Crystals]Ultraviolet", "52.8", "26.4", "0", "0"],
|
||||
["[Frequency Crystals]Standard", "44", "26.4", "0", "0"],
|
||||
["[Frequency Crystals]Infrared", "44", "17.6", "0", "0"],
|
||||
["[Frequency Crystals]Microwave", "35.2", "17.6", "0", "0"],
|
||||
["[Frequency Crystals]Radio", "44", "0", "0", "0"],
|
||||
["[Hybrid Charges][T2] Void", "0", "61.6", "61.6", "0"],
|
||||
["[Hybrid Charges][T2] Null", "0", "48", "40", "0"],
|
||||
["[Hybrid Charges][T2] Javelin", "0", "64", "48", "0"],
|
||||
["[Hybrid Charges][T2] Spike", "0", "32", "32", "0"],
|
||||
["[Hybrid Charges]Antimatter", "0", "48", "67.2", "0"],
|
||||
["[Hybrid Charges]Plutonium", "0", "48", "57.6", "0"],
|
||||
["[Hybrid Charges]Uranium", "0", "38.4", "57.6", "0"],
|
||||
["[Hybrid Charges]Thorium", "0", "38.4", "48", "0"],
|
||||
["[Hybrid Charges]Lead", "0", "28.8", "48", "0"],
|
||||
["[Hybrid Charges]Iridium", "0", "28.8", "38.4", "0"],
|
||||
["[Hybrid Charges]Tungsten", "0", "19.2", "38.4", "0"],
|
||||
["[Hybrid Charges]Iron", "0", "19.2", "28.8", "0"],
|
||||
["[Missiles]Mjolnir", "100", "0", "0", "0"],
|
||||
["[Missiles]Inferno", "0", "100", "0", "0"],
|
||||
["[Missiles]Scourge", "0", "0", "100", "0"],
|
||||
["[Missiles]Nova", "0", "0", "0", "100"],
|
||||
["[Missiles][Structure] Standup Missile", "100", "100", "100", "100"],
|
||||
["[Projectile Ammo][T2] Hail", "0", "0", "26.4", "96.8"],
|
||||
["[Projectile Ammo][T2] Barrage", "0", "0", "40", "48"],
|
||||
["[Projectile Ammo][T2] Quake", "0", "0", "40", "72"],
|
||||
["[Projectile Ammo][T2] Tremor", "0", "0", "24", "40"],
|
||||
["[Projectile Ammo]EMP", "79.2", "0", "8.8", "17.6"],
|
||||
["[Projectile Ammo]Phased Plasma", "0", "88", "17.6", "0"],
|
||||
["[Projectile Ammo]Fusion", "0", "0", "17.6", "88"],
|
||||
["[Projectile Ammo]Depleted Uranium", "0", "26.4", "17.6", "26.4"],
|
||||
["[Projectile Ammo]Titanium Sabot", "0", "0", "52.8", "176"],
|
||||
["[Projectile Ammo]Proton", "26.4", "0", "17.6", "0"],
|
||||
["[Projectile Ammo]Carbonized Lead", "0", "0", "35.2", "8.8"],
|
||||
["[Projectile Ammo]Nuclear", "0", "0", "8.8", "35.2"],
|
||||
damageProfileList = [["Uniform", 25, 25, 25, 25],
|
||||
["[Generic]EM", 1, 0, 0, 0],
|
||||
["[Generic]Thermal", 0, 1, 0, 0],
|
||||
["[Generic]Kinetic", 0, 0, 1, 0],
|
||||
["[Generic]Explosive", 0, 0, 0, 1],
|
||||
["[NPC][Asteroid] Blood Raiders", 5067, 4214, 0, 0],
|
||||
["[Bombs]Electron Bomb", 6400, 0, 0, 0],
|
||||
["[Bombs]Scorch Bomb", 0, 6400, 0, 0],
|
||||
["[Bombs]Concussion Bomb", 0, 0, 6400, 0],
|
||||
["[Bombs]Shrapnel Bomb", 0, 0, 0, 6400],
|
||||
["[Frequency Crystals][T2] Conflagration", 7.7, 7.7, 0, 0],
|
||||
["[Frequency Crystals][T2] Scorch", 9, 2, 0, 0],
|
||||
["[Frequency Crystals][T2] Gleam", 7, 7, 0, 0],
|
||||
["[Frequency Crystals][T2] Aurora", 5, 3, 0, 0],
|
||||
["[Frequency Crystals]Multifrequency", 7, 5, 0, 0],
|
||||
["[Frequency Crystals]Gamma", 7, 4, 0, 0],
|
||||
["[Frequency Crystals]Xray", 6, 4, 0, 0],
|
||||
["[Frequency Crystals]Ultraviolet", 6, 3, 0, 0],
|
||||
["[Frequency Crystals]Standard", 5, 3, 0, 0],
|
||||
["[Frequency Crystals]Infrared", 5, 2, 0, 0],
|
||||
["[Frequency Crystals]Microwave", 4, 2, 0, 0],
|
||||
["[Frequency Crystals]Radio", 5, 0, 0, 0],
|
||||
["[Hybrid Charges][T2] Void", 0, 7.7, 7.7, 0],
|
||||
["[Hybrid Charges][T2] Null", 0, 6, 5, 0],
|
||||
["[Hybrid Charges][T2] Javelin", 0, 8, 6, 0],
|
||||
["[Hybrid Charges][T2] Spike", 0, 4, 4, 0],
|
||||
["[Hybrid Charges]Antimatter", 0, 5, 7, 0],
|
||||
["[Hybrid Charges]Plutonium", 0, 5, 6, 0],
|
||||
["[Hybrid Charges]Uranium", 0, 4, 6, 0],
|
||||
["[Hybrid Charges]Thorium", 0, 4, 5, 0],
|
||||
["[Hybrid Charges]Lead", 0, 3, 5, 0],
|
||||
["[Hybrid Charges]Iridium", 0, 3, 4, 0],
|
||||
["[Hybrid Charges]Tungsten", 0, 2, 4, 0],
|
||||
["[Hybrid Charges]Iron", 0, 2, 3, 0],
|
||||
["[Missiles]Mjolnir", 1, 0, 0, 0],
|
||||
["[Missiles]Inferno", 0, 1, 0, 0],
|
||||
["[Missiles]Scourge", 0, 0, 1, 0],
|
||||
["[Missiles]Nova", 0, 0, 0, 1],
|
||||
["[Missiles][Structure] Standup Missile", 1, 1, 1, 1],
|
||||
["[Projectile Ammo][T2] Hail", 0, 0, 3.3, 12.1],
|
||||
["[Projectile Ammo][T2] Barrage", 0, 0, 5, 6],
|
||||
["[Projectile Ammo][T2] Quake", 0, 0, 5, 9],
|
||||
["[Projectile Ammo][T2] Tremor", 0, 0, 3, 5],
|
||||
["[Projectile Ammo]EMP", 9, 0, 1, 2],
|
||||
["[Projectile Ammo]Phased Plasma", 0, 10, 2, 0],
|
||||
["[Projectile Ammo]Fusion", 0, 0, 2, 10],
|
||||
["[Projectile Ammo]Depleted Uranium", 0, 3, 2, 3],
|
||||
["[Projectile Ammo]Titanium Sabot", 0, 0, 6, 2],
|
||||
["[Projectile Ammo]Proton", 3, 0, 2, 0],
|
||||
["[Projectile Ammo]Carbonized Lead", 0, 0, 4, 1],
|
||||
["[Projectile Ammo]Nuclear", 0, 0, 1, 4],
|
||||
# Different sizes of plasma do different damage, the values here are
|
||||
# average of proportions across sizes
|
||||
["[Exotic Plasma][T2] Occult", "0", "55863", "0", "44137"],
|
||||
["[Exotic Plasma][T2] Mystic", "0", "66319", "0", "33681"],
|
||||
["[Exotic Plasma]Tetryon", "0", "69208", "0", "30792"],
|
||||
["[Exotic Plasma]Baryon", "0", "59737", "0", "40263"],
|
||||
["[Exotic Plasma]Meson", "0", "60519", "0", "39481"],
|
||||
["[NPC][Burner] Cruor (Blood Raiders)", "90", "90", "0", "0"],
|
||||
["[NPC][Burner] Dramiel (Angel)", "55", "0", "20", "96"],
|
||||
["[NPC][Burner] Daredevil (Serpentis)", "0", "110", "154", "0"],
|
||||
["[NPC][Burner] Succubus (Sanshas Nation)", "135", "30", "0", "0"],
|
||||
["[NPC][Burner] Worm (Guristas)", "0", "0", "228", "0"],
|
||||
["[NPC][Burner] Enyo", "0", "147", "147", "0"],
|
||||
["[NPC][Burner] Hawk", "0", "0", "247", "0"],
|
||||
["[NPC][Burner] Jaguar", "36", "0", "50", "182"],
|
||||
["[NPC][Burner] Vengeance", "232", "0", "0", "0"],
|
||||
["[NPC][Burner] Ashimmu (Blood Raiders)", "260", "100", "0", "0"],
|
||||
["[NPC][Burner] Talos", "0", "413", "413", "0"],
|
||||
["[NPC][Burner] Sentinel", "0", "75", "0", "90"],
|
||||
["[NPC][Asteroid] Angel Cartel", "1838", "562", "2215", "3838"],
|
||||
["[NPC][Deadspace] Angel Cartel", "369", "533", "1395", "3302"],
|
||||
["[NPC][Deadspace] Blood Raiders", "6040", "5052", "10", "15"],
|
||||
["[NPC][Asteroid] Guristas", "0", "1828", "7413", "0"],
|
||||
["[NPC][Deadspace] Guristas", "0", "1531", "9680", "0"],
|
||||
["[NPC][Asteroid] Rogue Drone", "394", "666", "1090", "1687"],
|
||||
["[NPC][Deadspace] Rogue Drone", "276", "1071", "1069", "871"],
|
||||
["[NPC][Asteroid] Sanshas Nation", "5586", "4112", "0", "0"],
|
||||
["[NPC][Deadspace] Sanshas Nation", "3009", "2237", "0", "0"],
|
||||
["[NPC][Asteroid] Serpentis", "0", "5373", "4813", "0"],
|
||||
["[NPC][Deadspace] Serpentis", "0", "3110", "1929", "0"],
|
||||
["[NPC][Mission] Amarr Empire", "4464", "3546", "97", "0"],
|
||||
["[NPC][Mission] Caldari State", "0", "2139", "4867", "0"],
|
||||
["[NPC][Mission] CONCORD", "336", "134", "212", "412"],
|
||||
["[NPC][Mission] Gallente Federation", "9", "3712", "2758", "0"],
|
||||
["[NPC][Mission] Khanid", "612", "483", "43", "6"],
|
||||
["[NPC][Mission] Minmatar Republic", "1024", "388", "1655", "4285"],
|
||||
["[NPC][Mission] Mordus Legion", "25", "262", "625", "0"],
|
||||
["[NPC][Mission] Thukker", "0", "52", "10", "79"],
|
||||
["[NPC][Other] Sleepers", "1472", "1472", "1384", "1384"],
|
||||
["[NPC][Other] Sansha Incursion", "1682", "1347", "3678", "3678"]]
|
||||
["[Exotic Plasma][T2] Occult", 0, 55863, 0, 44137],
|
||||
["[Exotic Plasma][T2] Mystic", 0, 66319, 0, 33681],
|
||||
["[Exotic Plasma]Tetryon", 0, 69208, 0, 30792],
|
||||
["[Exotic Plasma]Baryon", 0, 59737, 0, 40263],
|
||||
["[Exotic Plasma]Meson", 0, 60519, 0, 39481],
|
||||
["[NPC][Burner] Cruor (Blood Raiders)", 90, 90, 0, 0],
|
||||
["[NPC][Burner] Dramiel (Angel)", 55, 0, 20, 96],
|
||||
["[NPC][Burner] Daredevil (Serpentis)", 0, 110, 154, 0],
|
||||
["[NPC][Burner] Succubus (Sanshas Nation)", 135, 30, 0, 0],
|
||||
["[NPC][Burner] Worm (Guristas)", 0, 0, 228, 0],
|
||||
["[NPC][Burner] Enyo", 0, 147, 147, 0],
|
||||
["[NPC][Burner] Hawk", 0, 0, 247, 0],
|
||||
["[NPC][Burner] Jaguar", 36, 0, 50, 182],
|
||||
["[NPC][Burner] Vengeance", 232, 0, 0, 0],
|
||||
["[NPC][Burner] Ashimmu (Blood Raiders)", 260, 100, 0, 0],
|
||||
["[NPC][Burner] Talos", 0, 413, 413, 0],
|
||||
["[NPC][Burner] Sentinel", 0, 75, 0, 90],
|
||||
["[NPC][Asteroid] Angel Cartel", 1838, 562, 2215, 3838],
|
||||
["[NPC][Deadspace] Angel Cartel", 369, 533, 1395, 3302],
|
||||
["[NPC][Deadspace] Blood Raiders", 6040, 5052, 10, 15],
|
||||
["[NPC][Asteroid] Guristas", 0, 1828, 7413, 0],
|
||||
["[NPC][Deadspace] Guristas", 0, 1531, 9680, 0],
|
||||
["[NPC][Asteroid] Rogue Drone", 394, 666, 1090, 1687],
|
||||
["[NPC][Deadspace] Rogue Drone", 276, 1071, 1069, 871],
|
||||
["[NPC][Asteroid] Sanshas Nation", 5586, 4112, 0, 0],
|
||||
["[NPC][Deadspace] Sanshas Nation", 3009, 2237, 0, 0],
|
||||
["[NPC][Asteroid] Serpentis", 0, 5373, 4813, 0],
|
||||
["[NPC][Deadspace] Serpentis", 0, 3110, 1929, 0],
|
||||
["[NPC][Mission] Amarr Empire", 4464, 3546, 97, 0],
|
||||
["[NPC][Mission] Caldari State", 0, 2139, 4867, 0],
|
||||
["[NPC][Mission] CONCORD", 336, 134, 212, 412],
|
||||
["[NPC][Mission] Gallente Federation", 9, 3712, 2758, 0],
|
||||
["[NPC][Mission] Khanid", 612, 483, 43, 6],
|
||||
["[NPC][Mission] Minmatar Republic", 1024, 388, 1655, 4285],
|
||||
["[NPC][Mission] Mordus Legion", 25, 262, 625, 0],
|
||||
["[NPC][Mission] Thukker", 0, 52, 10, 79],
|
||||
["[NPC][Other] Sleepers", 1472, 1472, 1384, 1384],
|
||||
["[NPC][Other] Sansha Incursion", 1682, 1347, 3678, 3678]]
|
||||
|
||||
for damageProfileRow in damageProfileList:
|
||||
name, em, therm, kin, exp = damageProfileRow
|
||||
@@ -130,61 +133,67 @@ class DefaultDatabaseValues:
|
||||
if damageProfile is None:
|
||||
damageProfile = es_DamagePattern(em, therm, kin, exp)
|
||||
damageProfile.name = name
|
||||
eos.db.save(damageProfile)
|
||||
eos.db.add(damageProfile)
|
||||
else:
|
||||
damageProfile.emAmount = em
|
||||
damageProfile.thermalAmount = therm
|
||||
damageProfile.kineticAmount = kin
|
||||
damageProfile.explosiveAmount = exp
|
||||
eos.db.commit()
|
||||
|
||||
@classmethod
|
||||
def importTargetProfileDefaults(cls):
|
||||
targetProfileList = [["Uniform (25%)", "0.25", "0.25", "0.25", "0.25"],
|
||||
["Uniform (50%)", "0.50", "0.50", "0.50", "0.50"],
|
||||
["Uniform (75%)", "0.75", "0.75", "0.75", "0.75"],
|
||||
["Uniform (90%)", "0.90", "0.90", "0.90", "0.90"],
|
||||
["[T1 Resist]Shield", "0.0", "0.20", "0.40", "0.50"],
|
||||
["[T1 Resist]Armor", "0.50", "0.45", "0.25", "0.10"],
|
||||
["[T1 Resist]Hull", "0.33", "0.33", "0.33", "0.33"],
|
||||
["[T1 Resist]Shield (+T2 DCU)", "0.125", "0.30", "0.475", "0.562"],
|
||||
["[T1 Resist]Armor (+T2 DCU)", "0.575", "0.532", "0.363", "0.235"],
|
||||
["[T1 Resist]Hull (+T2 DCU)", "0.598", "0.598", "0.598", "0.598"],
|
||||
["[T2 Resist]Amarr (Shield)", "0.0", "0.20", "0.70", "0.875"],
|
||||
["[T2 Resist]Amarr (Armor)", "0.50", "0.35", "0.625", "0.80"],
|
||||
["[T2 Resist]Caldari (Shield)", "0.20", "0.84", "0.76", "0.60"],
|
||||
["[T2 Resist]Caldari (Armor)", "0.50", "0.8625", "0.625", "0.10"],
|
||||
["[T2 Resist]Gallente (Shield)", "0.0", "0.60", "0.85", "0.50"],
|
||||
["[T2 Resist]Gallente (Armor)", "0.50", "0.675", "0.8375", "0.10"],
|
||||
["[T2 Resist]Minmatar (Shield)", "0.75", "0.60", "0.40", "0.50"],
|
||||
["[T2 Resist]Minmatar (Armor)", "0.90", "0.675", "0.25", "0.10"],
|
||||
["[NPC][Asteroid] Angel Cartel", "0.54", "0.42", "0.37", "0.32"],
|
||||
["[NPC][Asteroid] Blood Raiders", "0.34", "0.39", "0.45", "0.52"],
|
||||
["[NPC][Asteroid] Guristas", "0.55", "0.35", "0.3", "0.48"],
|
||||
["[NPC][Asteroid] Rogue Drones", "0.35", "0.38", "0.44", "0.49"],
|
||||
["[NPC][Asteroid] Sanshas Nation", "0.35", "0.4", "0.47", "0.53"],
|
||||
["[NPC][Asteroid] Serpentis", "0.49", "0.38", "0.29", "0.51"],
|
||||
["[NPC][Deadspace] Angel Cartel", "0.59", "0.48", "0.4", "0.32"],
|
||||
["[NPC][Deadspace] Blood Raiders", "0.31", "0.39", "0.47", "0.56"],
|
||||
["[NPC][Deadspace] Guristas", "0.57", "0.39", "0.31", "0.5"],
|
||||
["[NPC][Deadspace] Rogue Drones", "0.42", "0.42", "0.47", "0.49"],
|
||||
["[NPC][Deadspace] Sanshas Nation", "0.31", "0.39", "0.47", "0.56"],
|
||||
["[NPC][Deadspace] Serpentis", "0.49", "0.38", "0.29", "0.56"],
|
||||
["[NPC][Mission] Amarr Empire", "0.34", "0.38", "0.42", "0.46"],
|
||||
["[NPC][Mission] Caldari State", "0.51", "0.38", "0.3", "0.51"],
|
||||
["[NPC][Mission] CONCORD", "0.47", "0.46", "0.47", "0.47"],
|
||||
["[NPC][Mission] Gallente Federation", "0.51", "0.38", "0.31", "0.52"],
|
||||
["[NPC][Mission] Khanid", "0.51", "0.42", "0.36", "0.4"],
|
||||
["[NPC][Mission] Minmatar Republic", "0.51", "0.46", "0.41", "0.35"],
|
||||
["[NPC][Mission] Mordus Legion", "0.32", "0.48", "0.4", "0.62"],
|
||||
["[NPC][Other] Sleeper", "0.61", "0.61", "0.61", "0.61"],
|
||||
["[NPC][Other] Sansha Incursion", "0.65", "0.63", "0.64", "0.65"],
|
||||
["[NPC][Burner] Cruor (Blood Raiders)", "0.8", "0.73", "0.69", "0.67"],
|
||||
["[NPC][Burner] Dramiel (Angel)", "0.35", "0.48", "0.61", "0.68"],
|
||||
["[NPC][Burner] Daredevil (Serpentis)", "0.69", "0.59", "0.59", "0.43"],
|
||||
["[NPC][Burner] Succubus (Sanshas Nation)", "0.35", "0.48", "0.61", "0.68"],
|
||||
["[NPC][Burner] Worm (Guristas)", "0.48", "0.58", "0.69", "0.74"],
|
||||
["[NPC][Burner] Enyo", "0.58", "0.72", "0.86", "0.24"],
|
||||
["[NPC][Burner] Hawk", "0.3", "0.86", "0.79", "0.65"],
|
||||
["[NPC][Burner] Jaguar", "0.78", "0.65", "0.48", "0.56"],
|
||||
["[NPC][Burner] Vengeance", "0.66", "0.56", "0.75", "0.86"],
|
||||
["[NPC][Burner] Ashimmu (Blood Raiders)", "0.8", "0.76", "0.68", "0.7"],
|
||||
["[NPC][Burner] Talos", "0.68", "0.59", "0.59", "0.43"],
|
||||
["[NPC][Burner] Sentinel", "0.58", "0.45", "0.52", "0.66"]]
|
||||
targetProfileList = [["Uniform (25%)", 0.25, 0.25, 0.25, 0.25],
|
||||
["Uniform (50%)", 0.50, 0.50, 0.50, 0.50],
|
||||
["Uniform (75%)", 0.75, 0.75, 0.75, 0.75],
|
||||
["Uniform (90%)", 0.90, 0.90, 0.90, 0.90],
|
||||
["[T1 Resist]Shield", 0.0, 0.20, 0.40, 0.50],
|
||||
["[T1 Resist]Armor", 0.50, 0.45, 0.25, 0.10],
|
||||
["[T1 Resist]Hull", 0.33, 0.33, 0.33, 0.33],
|
||||
["[T1 Resist]Shield (+T2 DCU)", 0.125, 0.30, 0.475, 0.562],
|
||||
["[T1 Resist]Armor (+T2 DCU)", 0.575, 0.532, 0.363, 0.235],
|
||||
["[T1 Resist]Hull (+T2 DCU)", 0.598, 0.598, 0.598, 0.598],
|
||||
["[T2 Resist]Amarr (Shield)", 0.0, 0.20, 0.70, 0.875],
|
||||
["[T2 Resist]Amarr (Armor)", 0.50, 0.35, 0.625, 0.80],
|
||||
["[T2 Resist]Caldari (Shield)", 0.20, 0.84, 0.76, 0.60],
|
||||
["[T2 Resist]Caldari (Armor)", 0.50, 0.8625, 0.625, 0.10],
|
||||
["[T2 Resist]Gallente (Shield)", 0.0, 0.60, 0.85, 0.50],
|
||||
["[T2 Resist]Gallente (Armor)", 0.50, 0.675, 0.8375, 0.10],
|
||||
["[T2 Resist]Minmatar (Shield)", 0.75, 0.60, 0.40, 0.50],
|
||||
["[T2 Resist]Minmatar (Armor)", 0.90, 0.675, 0.25, 0.10],
|
||||
["[NPC][Asteroid] Angel Cartel", 0.54, 0.42, 0.37, 0.32],
|
||||
["[NPC][Asteroid] Blood Raiders", 0.34, 0.39, 0.45, 0.52],
|
||||
["[NPC][Asteroid] Guristas", 0.55, 0.35, 0.3, 0.48],
|
||||
["[NPC][Asteroid] Rogue Drones", 0.35, 0.38, 0.44, 0.49],
|
||||
["[NPC][Asteroid] Sanshas Nation", 0.35, 0.4, 0.47, 0.53],
|
||||
["[NPC][Asteroid] Serpentis", 0.49, 0.38, 0.29, 0.51],
|
||||
["[NPC][Deadspace] Angel Cartel", 0.59, 0.48, 0.4, 0.32],
|
||||
["[NPC][Deadspace] Blood Raiders", 0.31, 0.39, 0.47, 0.56],
|
||||
["[NPC][Deadspace] Guristas", 0.57, 0.39, 0.31, 0.5],
|
||||
["[NPC][Deadspace] Rogue Drones", 0.42, 0.42, 0.47, 0.49],
|
||||
["[NPC][Deadspace] Sanshas Nation", 0.31, 0.39, 0.47, 0.56],
|
||||
["[NPC][Deadspace] Serpentis", 0.49, 0.38, 0.29, 0.56],
|
||||
["[NPC][Mission] Amarr Empire", 0.34, 0.38, 0.42, 0.46],
|
||||
["[NPC][Mission] Caldari State", 0.51, 0.38, 0.3, 0.51],
|
||||
["[NPC][Mission] CONCORD", 0.47, 0.46, 0.47, 0.47],
|
||||
["[NPC][Mission] Gallente Federation", 0.51, 0.38, 0.31, 0.52],
|
||||
["[NPC][Mission] Khanid", 0.51, 0.42, 0.36, 0.4],
|
||||
["[NPC][Mission] Minmatar Republic", 0.51, 0.46, 0.41, 0.35],
|
||||
["[NPC][Mission] Mordus Legion", 0.32, 0.48, 0.4, 0.62],
|
||||
["[NPC][Other] Sleeper", 0.61, 0.61, 0.61, 0.61],
|
||||
["[NPC][Other] Sansha Incursion", 0.65, 0.63, 0.64, 0.65],
|
||||
["[NPC][Burner] Cruor (Blood Raiders)", 0.8, 0.73, 0.69, 0.67],
|
||||
["[NPC][Burner] Dramiel (Angel)", 0.35, 0.48, 0.61, 0.68],
|
||||
["[NPC][Burner] Daredevil (Serpentis)", 0.69, 0.59, 0.59, 0.43],
|
||||
["[NPC][Burner] Succubus (Sanshas Nation)", 0.35, 0.48, 0.61, 0.68],
|
||||
["[NPC][Burner] Worm (Guristas)", 0.48, 0.58, 0.69, 0.74],
|
||||
["[NPC][Burner] Enyo", 0.58, 0.72, 0.86, 0.24],
|
||||
["[NPC][Burner] Hawk", 0.3, 0.86, 0.79, 0.65],
|
||||
["[NPC][Burner] Jaguar", 0.78, 0.65, 0.48, 0.56],
|
||||
["[NPC][Burner] Vengeance", 0.66, 0.56, 0.75, 0.86],
|
||||
["[NPC][Burner] Ashimmu (Blood Raiders)", 0.8, 0.76, 0.68, 0.7],
|
||||
["[NPC][Burner] Talos", 0.68, 0.59, 0.59, 0.43],
|
||||
["[NPC][Burner] Sentinel", 0.58, 0.45, 0.52, 0.66]]
|
||||
|
||||
for targetProfileRow in targetProfileList:
|
||||
name = targetProfileRow[0]
|
||||
@@ -204,15 +213,25 @@ class DefaultDatabaseValues:
|
||||
radius = targetProfileRow[7]
|
||||
except:
|
||||
radius = None
|
||||
targetProfile = eos.db.eos.db.getTargetProfile(name)
|
||||
targetProfile = eos.db.getTargetProfile(name)
|
||||
if targetProfile is None:
|
||||
targetProfile = es_TargetProfile(em, therm, kin, exp, maxVel, sigRad, radius)
|
||||
targetProfile.name = name
|
||||
eos.db.save(targetProfile)
|
||||
eos.db.add(targetProfile)
|
||||
else:
|
||||
targetProfile.emAmount = em
|
||||
targetProfile.thermalAmount = therm
|
||||
targetProfile.kineticAmount = kin
|
||||
targetProfile.explosiveAmount = exp
|
||||
targetProfile.maxVelocity = maxVel
|
||||
targetProfile.signatureRadius = sigRad
|
||||
targetProfile.radius = radius
|
||||
eos.db.commit()
|
||||
|
||||
|
||||
@classmethod
|
||||
def importRequiredDefaults(cls):
|
||||
damageProfileList = [["Uniform", "25", "25", "25", "25"]]
|
||||
damageProfileList = [["Uniform", 25, 25, 25, 25]]
|
||||
|
||||
for damageProfileRow in damageProfileList:
|
||||
name, em, therm, kin, exp = damageProfileRow
|
||||
@@ -220,4 +239,10 @@ class DefaultDatabaseValues:
|
||||
if damageProfile is None:
|
||||
damageProfile = es_DamagePattern(em, therm, kin, exp)
|
||||
damageProfile.name = name
|
||||
eos.db.save(damageProfile)
|
||||
eos.db.add(damageProfile)
|
||||
else:
|
||||
damageProfile.emAmount = em
|
||||
damageProfile.thermalAmount = therm
|
||||
damageProfile.kineticAmount = kin
|
||||
damageProfile.explosiveAmount = exp
|
||||
eos.db.commit()
|
||||
|
||||
@@ -42,6 +42,7 @@ modules_table = Table("modules", saveddata_meta,
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("spoolType", Integer, nullable=True),
|
||||
Column("spoolAmount", Float, nullable=True),
|
||||
Column("projectionRange", Float, nullable=True),
|
||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||
|
||||
mapper(Module, modules_table,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm.collections import collection
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -138,9 +139,10 @@ class HandledModuleList(HandledList):
|
||||
else:
|
||||
self.appendIgnoreEmpty(mod)
|
||||
|
||||
@collection.appender
|
||||
def appendIgnoreEmpty(self, mod):
|
||||
mod.position = len(self)
|
||||
HandledList.append(self, mod)
|
||||
super().append(mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
|
||||
|
||||
4826
eos/effects.py
4826
eos/effects.py
File diff suppressed because it is too large
Load Diff
@@ -530,6 +530,14 @@ class Item(EqBase):
|
||||
def isBooster(self):
|
||||
return self.group.name == 'Booster' and self.category.name == 'Implant'
|
||||
|
||||
@property
|
||||
def isStandup(self):
|
||||
if self.category.name == "Structure Module":
|
||||
return True
|
||||
if self.isFighter and {'fighterSquadronIsStandupLight', 'fighterSquadronIsStandupHeavy', 'fighterSquadronIsStandupSupport'}.intersection(self.attributes):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "Item(ID={}, name={}) at {}".format(
|
||||
self.ID, self.name, hex(id(self))
|
||||
@@ -601,10 +609,6 @@ class MetaGroup(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class MetaType(EqBase):
|
||||
pass
|
||||
|
||||
|
||||
class Unit(EqBase):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -122,7 +122,7 @@ class Booster(HandledItem, ItemAttrShortcut):
|
||||
(effect.isType("passive") or effect.isType("boosterSideEffect")):
|
||||
if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects:
|
||||
continue
|
||||
effect.handler(fit, self, ("booster",))
|
||||
effect.handler(fit, self, ("booster",), None, effect=effect)
|
||||
|
||||
@validates("ID", "itemID", "ammoID", "active")
|
||||
def validator(self, key, val):
|
||||
|
||||
@@ -422,7 +422,7 @@ class Skill(HandledItem):
|
||||
(not fit.isStructure or effect.isType("structure")) and \
|
||||
effect.activeByDefault:
|
||||
try:
|
||||
effect.handler(fit, self, ("skill",))
|
||||
effect.handler(fit, self, ("skill",), None, effect=effect)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -25,6 +26,7 @@ import eos.db
|
||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.utils.cycles import CycleInfo
|
||||
from eos.utils.default import DEFAULT
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
|
||||
|
||||
@@ -45,6 +47,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.amount = 0
|
||||
self.amountActive = 0
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.build()
|
||||
|
||||
@reconstructor
|
||||
@@ -304,7 +307,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return True
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||
if self.projected or forceProjected:
|
||||
context = "projected", "drone"
|
||||
projected = True
|
||||
@@ -312,6 +315,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("drone",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
effect.activeByDefault and \
|
||||
@@ -319,36 +324,34 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
projected is False and effect.isType("passive")):
|
||||
# See GH issue #765
|
||||
if effect.getattr('grouped'):
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
else:
|
||||
i = 0
|
||||
while i != self.amountActive:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
i += 1
|
||||
|
||||
if self.charge:
|
||||
for effect in self.charge.effects.values():
|
||||
if effect.runTime == runTime and effect.activeByDefault:
|
||||
effect.handler(fit, self, ("droneCharge",))
|
||||
effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Drone(self.item)
|
||||
copy.amount = self.amount
|
||||
copy.amountActive = self.amountActive
|
||||
copy.projectionRange = self.projectionRange
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self.amount
|
||||
amountActive = self.amountActive
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Drone.__init__(self, item)
|
||||
self.amount = amount
|
||||
self.amountActive = amountActive
|
||||
self.projectionRange = projectionRange
|
||||
|
||||
def fits(self, fit):
|
||||
fitDroneGroupLimits = set()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
@@ -27,8 +28,9 @@ from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||
from eos.utils.stats import DmgTypes
|
||||
from eos.utils.default import DEFAULT
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
@@ -47,6 +49,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
self.itemID = item.ID if item is not None else None
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.active = True
|
||||
|
||||
# -1 is a placeholder that represents max squadron size, which we may not know yet as ships may modify this with
|
||||
@@ -380,7 +383,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return True
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, forcedProjRange=DEFAULT):
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
@@ -391,6 +394,8 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("fighter",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
for ability in self.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
@@ -399,17 +404,11 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if effect.runTime == runTime and effect.activeByDefault and \
|
||||
((projected and effect.isType("projected")) or not projected):
|
||||
if ability.grouped:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
else:
|
||||
i = 0
|
||||
while i != self.amount:
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
i += 1
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
@@ -419,18 +418,22 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
for ability in self.abilities:
|
||||
copyAbility = next(filter(lambda a: a.effectID == ability.effectID, copy.abilities))
|
||||
copyAbility.active = ability.active
|
||||
copy.projectionRange = self.projectionRange
|
||||
return copy
|
||||
|
||||
def rebase(self, item):
|
||||
amount = self._amount
|
||||
active = self.active
|
||||
abilityEffectStates = {a.effectID: a.active for a in self.abilities}
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Fighter.__init__(self, item)
|
||||
self._amount = amount
|
||||
self.active = active
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in abilityEffectStates:
|
||||
ability.active = abilityEffectStates[ability.effectID]
|
||||
self.projectionRange = projectionRange
|
||||
|
||||
def fits(self, fit):
|
||||
# If ships doesn't support this type of fighter, don't add it
|
||||
|
||||
@@ -21,13 +21,14 @@ import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import log, sqrt
|
||||
|
||||
from logbook import Logger
|
||||
from math import asinh, log, sqrt
|
||||
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,
|
||||
@@ -440,10 +441,8 @@ class Fit:
|
||||
return False
|
||||
|
||||
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
|
||||
if isinstance(self.ship, Citadel) and item.category.name != "Structure Module" or \
|
||||
not isinstance(self.ship, Citadel) and item.category.name == "Structure Module":
|
||||
if isinstance(self.ship, Citadel) is not item.isStandup:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def clear(self, projected=False, command=False):
|
||||
@@ -934,13 +933,20 @@ class Fit:
|
||||
To support a simpler way of doing self projections (so that we don't have to make a copy of the fit and
|
||||
recalculate), this function was developed to be a common source of projected effect application.
|
||||
"""
|
||||
c = chain(self.drones, self.fighters, self.modules)
|
||||
for item in c:
|
||||
for item in chain(self.drones, self.fighters):
|
||||
if item is not None:
|
||||
# apply effects onto target fit x amount of times
|
||||
for _ in range(projectionInfo.amount):
|
||||
targetFit.register(item, origin=self)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
item.calculateModifiedAttributes(
|
||||
targetFit, runTime, forceProjected=True,
|
||||
forcedProjRange=0)
|
||||
for mod in self.modules:
|
||||
for _ in range(projectionInfo.amount):
|
||||
targetFit.register(mod, origin=self)
|
||||
mod.calculateModifiedAttributes(
|
||||
targetFit, runTime, forceProjected=True,
|
||||
forcedProjRange=projectionInfo.projectionRange)
|
||||
|
||||
def fill(self):
|
||||
"""
|
||||
@@ -1219,8 +1225,8 @@ class Fit:
|
||||
# Signature reduction, uses the bomb formula as per CCP Larrikin
|
||||
if energyNeutralizerSignatureResolution:
|
||||
capNeed = capNeed * min(1, signatureRadius / energyNeutralizerSignatureResolution)
|
||||
|
||||
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
||||
if capNeed:
|
||||
self.__extraDrains.append((cycleTime, capNeed, clipSize, reloadTime))
|
||||
|
||||
def removeDrain(self, i):
|
||||
del self.__extraDrains[i]
|
||||
@@ -1524,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
|
||||
|
||||
@@ -1621,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
|
||||
@@ -1671,6 +1691,8 @@ class Fit:
|
||||
copyProjectionInfo = fit.getProjectionInfo(fitCopy.ID)
|
||||
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
||||
copyProjectionInfo.active = originalProjectionInfo.active
|
||||
copyProjectionInfo.amount = originalProjectionInfo.amount
|
||||
copyProjectionInfo.projectionRange = originalProjectionInfo.projectionRange
|
||||
forceUpdateSavedata(fit)
|
||||
|
||||
return fitCopy
|
||||
|
||||
@@ -95,7 +95,7 @@ class Implant(HandledItem, ItemAttrShortcut):
|
||||
return
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and effect.isType("passive") and effect.activeByDefault:
|
||||
effect.handler(fit, self, ("implant",))
|
||||
effect.handler(fit, self, ("implant",), None, effect=effect)
|
||||
|
||||
@validates("fitID", "itemID", "active")
|
||||
def validator(self, key, val):
|
||||
|
||||
@@ -54,7 +54,7 @@ class Mode(ItemAttrShortcut, HandledItem):
|
||||
if self.item:
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and effect.activeByDefault:
|
||||
effect.handler(fit, self, context=("module",))
|
||||
effect.handler(fit, self, ("module",), None, effect=effect)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Mode(self.item)
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from logbook import Logger
|
||||
import math
|
||||
|
||||
from logbook import Logger
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
@@ -28,6 +29,7 @@ from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, Modi
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||
from eos.utils.default import DEFAULT
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||
from eos.utils.stats import DmgTypes, RRTypes
|
||||
@@ -90,6 +92,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.__charge = None
|
||||
|
||||
self.projected = False
|
||||
self.projectionRange = None
|
||||
self.state = FittingModuleState.ONLINE
|
||||
self.build()
|
||||
|
||||
@@ -837,7 +840,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False):
|
||||
def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False, forcedProjRange=DEFAULT):
|
||||
# We will run the effect when two conditions are met:
|
||||
# 1: It makes sense to run the effect
|
||||
# The effect is either offline
|
||||
@@ -854,6 +857,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("module",)
|
||||
projected = False
|
||||
|
||||
projectionRange = self.projectionRange if forcedProjRange is DEFAULT else forcedProjRange
|
||||
|
||||
if self.charge is not None:
|
||||
# fix for #82 and it's regression #106
|
||||
if not projected or (self.projected and not forceProjected) or gang:
|
||||
@@ -867,13 +872,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
(not gang or (gang and effect.isType("gang")))
|
||||
):
|
||||
contexts = ("moduleCharge",)
|
||||
# For gang effects, we pass in the effect itself as an argument. However, to avoid going through all
|
||||
# the effect definitions and defining this argument, do a simple try/catch here and be done with it.
|
||||
# @todo: possibly fix this
|
||||
try:
|
||||
effect.handler(fit, self, contexts, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, contexts)
|
||||
effect.handler(fit, self, contexts, projectionRange, effect=effect)
|
||||
|
||||
if self.item:
|
||||
if self.state >= FittingModuleState.OVERHEATED:
|
||||
@@ -883,7 +882,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
and not forceProjected \
|
||||
and effect.activeByDefault \
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
@@ -893,10 +892,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
|
||||
and ((projected and effect.isType("projected")) or not projected) \
|
||||
and ((gang and effect.isType("gang")) or not gang):
|
||||
try:
|
||||
effect.handler(fit, self, context, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
effect.handler(fit, self, context, projectionRange, effect=effect)
|
||||
|
||||
def getCycleParameters(self, reloadOverride=None):
|
||||
"""Copied from new eos as well"""
|
||||
@@ -1027,6 +1023,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copy.state = self.state
|
||||
copy.spoolType = self.spoolType
|
||||
copy.spoolAmount = self.spoolAmount
|
||||
copy.projectionRange = self.projectionRange
|
||||
|
||||
for x in self.mutators.values():
|
||||
Mutator(copy, x.attribute, x.value)
|
||||
@@ -1036,10 +1033,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def rebase(self, item):
|
||||
state = self.state
|
||||
charge = self.charge
|
||||
spoolType = self.spoolType
|
||||
spoolAmount = self.spoolAmount
|
||||
projectionRange = self.projectionRange
|
||||
|
||||
Module.__init__(self, item, self.baseItem, self.mutaplasmid)
|
||||
self.state = state
|
||||
if self.isValidCharge(charge):
|
||||
self.charge = charge
|
||||
self.spoolType = spoolType
|
||||
self.spoolAmount = spoolAmount
|
||||
self.projectionRange = projectionRange
|
||||
for x in self.mutators.values():
|
||||
Mutator(self, x.attribute, x.value)
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
# skillbook modifiers will use the stale modifier value
|
||||
# GH issue #351
|
||||
fit.register(self)
|
||||
effect.handler(fit, self, ("ship",))
|
||||
effect.handler(fit, self, ("ship",), None, effect=effect)
|
||||
|
||||
def validateModeItem(self, item, owner=None):
|
||||
""" Checks if provided item is a valid mode """
|
||||
|
||||
3
eos/utils/default.py
Normal file
3
eos/utils/default.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class DEFAULT:
|
||||
"""Singleton class to signify default argument value."""
|
||||
pass
|
||||
@@ -18,48 +18,20 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
def checkLockRange(src, distance):
|
||||
if distance is None:
|
||||
return 1
|
||||
if srcFalloffRange > 0:
|
||||
# Most modules cannot be activated when at 3x falloff range, with few exceptions like guns
|
||||
if restrictedRange and distance > srcOptimalRange + 3 * srcFalloffRange:
|
||||
return 0
|
||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||
elif distance <= srcOptimalRange:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
return True
|
||||
if GraphSettings.getInstance().get('ignoreLockRange'):
|
||||
return True
|
||||
return distance <= src.item.maxTargetRange
|
||||
|
||||
|
||||
# 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 checkDroneControlRange(src, distance):
|
||||
if distance is None:
|
||||
return True
|
||||
if GraphSettings.getInstance().get('ignoreDCR'):
|
||||
return True
|
||||
return distance <= src.item.extraAttributes['droneControlRange']
|
||||
|
||||
@@ -26,3 +26,5 @@ from . import fitCapacitor
|
||||
from . import fitMobility
|
||||
from . import fitWarpTime
|
||||
from . import fitLockTime
|
||||
# Hidden graphs, available via ctrl-alt-g
|
||||
from . import fitEcmBurstScanresDamps
|
||||
|
||||
@@ -29,6 +29,7 @@ from service.const import GraphCacheCleanupReason
|
||||
class FitGraph(metaclass=ABCMeta):
|
||||
|
||||
# UI stuff
|
||||
hidden = False
|
||||
views = []
|
||||
viewMap = {}
|
||||
|
||||
|
||||
@@ -21,37 +21,47 @@
|
||||
import math
|
||||
from functools import lru_cache
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from service.attribute import Attribute
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
applicationMap = {}
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isDealingDamage():
|
||||
continue
|
||||
if mod.hardpoint == FittingHardpoint.TURRET:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getTurretMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.hardpoint == FittingHardpoint.MISSILE:
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# FoF missiles can shoot beyond lock range
|
||||
if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects):
|
||||
applicationMap[mod] = getLauncherMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
|
||||
applicationMap[mod] = getSmartbombMult(
|
||||
mod=mod,
|
||||
@@ -64,44 +74,58 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
elif mod.item.group.name == 'Structure Guided Bomb Launcher':
|
||||
applicationMap[mod] = getGuidedBombMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange:
|
||||
applicationMap[mod] = getGuidedBombMult(
|
||||
mod=mod,
|
||||
src=src,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[mod] = 0
|
||||
elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
|
||||
applicationMap[mod] = getDoomsdayMult(
|
||||
mod=mod,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# Only single-target DDs need locks
|
||||
if not inLockRange and {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(mod.item.effects):
|
||||
applicationMap[mod] = 0
|
||||
else:
|
||||
applicationMap[mod] = getDoomsdayMult(
|
||||
mod=mod,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isDealingDamage():
|
||||
continue
|
||||
applicationMap[drone] = getDroneMult(
|
||||
drone=drone,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
if inLockRange and inDroneRange:
|
||||
applicationMap[drone] = getDroneMult(
|
||||
drone=drone,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
atkSpeed=atkSpeed,
|
||||
atkAngle=atkAngle,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtAngle=tgtAngle,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[drone] = 0
|
||||
for fighter in src.item.activeFightersIter():
|
||||
if not fighter.isDealingDamage():
|
||||
continue
|
||||
for ability in fighter.abilities:
|
||||
if not ability.dealsDamage or not ability.active:
|
||||
continue
|
||||
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
||||
fighter=fighter,
|
||||
ability=ability,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
# Bomb launching doesn't need locks
|
||||
if inLockRange or ability.effect.name == 'fighterAbilityLaunchBomb':
|
||||
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
|
||||
fighter=fighter,
|
||||
ability=ability,
|
||||
src=src,
|
||||
tgt=tgt,
|
||||
distance=distance,
|
||||
tgtSpeed=tgtSpeed,
|
||||
tgtSigRadius=tgtSigRadius)
|
||||
else:
|
||||
applicationMap[(fighter, ability.effectID)] = 0
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
for k, v in applicationMap.items():
|
||||
applicationMap[k] = floatUnerr(v)
|
||||
@@ -200,7 +224,11 @@ def getGuidedBombMult(mod, src, distance, tgtSigRadius):
|
||||
|
||||
|
||||
def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
|
||||
if distance is not None and distance > src.item.extraAttributes['droneControlRange']:
|
||||
if (
|
||||
distance is not None and (
|
||||
(not GraphSettings.getInstance().get('ignoreDCR') and distance > src.item.extraAttributes['droneControlRange']) or
|
||||
(not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange))
|
||||
):
|
||||
return 0
|
||||
droneSpeed = drone.getModifiedItemAttr('maxVelocity')
|
||||
# Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
|
||||
import math
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
|
||||
@@ -75,26 +76,29 @@ def getTackledSpeed(src, tgt, currentUntackledSpeed, srcScramRange, tgtScrammabl
|
||||
# What's immobile cannot be slowed
|
||||
if maxUntackledSpeed == 0:
|
||||
return maxUntackledSpeed
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
speedRatio = currentUntackledSpeed / maxUntackledSpeed
|
||||
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||
if srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
tgtScrammables = ()
|
||||
appliedMultipliers = {}
|
||||
# Modules first, they are applied always the same way
|
||||
for wData in webMods:
|
||||
appliedBoost = wData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=wData.optimal,
|
||||
srcFalloffRange=wData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||
# Modules first, they are always applied the same way
|
||||
if inLockRange:
|
||||
for wData in webMods:
|
||||
appliedBoost = wData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=wData.optimal,
|
||||
srcFalloffRange=wData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||
maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables)
|
||||
currentTackledSpeed = maxTackledSpeed * speedRatio
|
||||
# Drones and fighters
|
||||
mobileWebs = []
|
||||
mobileWebs.extend(webFighters)
|
||||
# Drones have range limit
|
||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
||||
if inLockRange:
|
||||
mobileWebs.extend(webFighters)
|
||||
if inLockRange and inDroneRange:
|
||||
mobileWebs.extend(webDrones)
|
||||
atkRadius = src.getRadius()
|
||||
# As mobile webs either follow the target or stick to the attacking ship,
|
||||
@@ -138,24 +142,27 @@ def getSigRadiusMult(src, tgt, tgtSpeed, srcScramRange, tgtScrammables, tpMods,
|
||||
# Can blow non-immune ships and target profiles
|
||||
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
|
||||
return 1
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
initSig = tgt.getSigRadius()
|
||||
# No scrams or distance is longer than longest scram - nullify scrammables list
|
||||
if srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange):
|
||||
tgtScrammables = ()
|
||||
# TPing modules
|
||||
appliedMultipliers = {}
|
||||
for tpData in tpMods:
|
||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=tpData.optimal,
|
||||
srcFalloffRange=tpData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||
if inLockRange:
|
||||
for tpData in tpMods:
|
||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=tpData.optimal,
|
||||
srcFalloffRange=tpData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||
# TPing drones
|
||||
mobileTps = []
|
||||
mobileTps.extend(tpFighters)
|
||||
# Drones have range limit
|
||||
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
|
||||
if inLockRange:
|
||||
mobileTps.extend(tpFighters)
|
||||
if inLockRange and inDroneRange:
|
||||
mobileTps.extend(tpDrones)
|
||||
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
|
||||
atkRadius = src.getRadius()
|
||||
|
||||
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
24
graphs/data/fitEcmBurstScanresDamps/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from .graph import FitEcmBurstScanresDampsGraph
|
||||
|
||||
|
||||
FitEcmBurstScanresDampsGraph.register()
|
||||
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
117
graphs/data/fitEcmBurstScanresDamps/getter.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.calc import calculateLockTime
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
ECM_BURST_DURATION = 30
|
||||
DRONE_LOCK_TIME = 2
|
||||
|
||||
|
||||
class TgtScanRes2TgtLockTimeGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
if miscParams['applyDamps']:
|
||||
tgtScanResMult = src.item.getDampMultScanRes()
|
||||
else:
|
||||
tgtScanResMult = 1
|
||||
return {
|
||||
'tgtScanResMult': tgtScanResMult,
|
||||
'sigRadius': src.item.ship.getModifiedItemAttr('signatureRadius')}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
scanRes = x
|
||||
time = calculateLockTime(
|
||||
srcScanRes=scanRes * commonData['tgtScanResMult'],
|
||||
tgtSigRadius=commonData['sigRadius'])
|
||||
return time
|
||||
|
||||
|
||||
class TgtScanRes2TgtLockUptimeGetter(TgtScanRes2TgtLockTimeGetter):
|
||||
|
||||
def _calculatePoint(self, *args, **kwargs):
|
||||
# Assuming you ECM burst every 30 seconds, find out how long you
|
||||
# will be locked before you burst another time
|
||||
lockTime = super()._calculatePoint(*args, **kwargs)
|
||||
lockedTime = max(0, ECM_BURST_DURATION - lockTime)
|
||||
return lockedTime
|
||||
|
||||
|
||||
class SrcDmgBaseGetter(SmoothPointGetter):
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
if miscParams['applyDamps']:
|
||||
tgtScanResMult = src.item.getDampMultScanRes()
|
||||
else:
|
||||
tgtScanResMult = 1
|
||||
return {
|
||||
'tgtScanResMult': tgtScanResMult,
|
||||
'srcSigRadius': src.item.ship.getModifiedItemAttr('signatureRadius'),
|
||||
'srcEhp': sum(src.item.ehp.values()),
|
||||
'srcDpsWeapon': src.item.getWeaponDps().total,
|
||||
'srcDpsDrone': src.item.getDroneDps().total if miscParams['applyDrones'] else 0}
|
||||
|
||||
@staticmethod
|
||||
def _calculateInflictedDamage(srcSigRadius, srcWeaponDps, srcDroneDps, srcEhp, tgtScanRes, tgtDps, uptimeAdjustment, uptimeAmountLimit):
|
||||
lockTime = calculateLockTime(srcScanRes=tgtScanRes, tgtSigRadius=srcSigRadius)
|
||||
lockUptime = max(0, ECM_BURST_DURATION - lockTime - uptimeAdjustment)
|
||||
lockDowntime = ECM_BURST_DURATION - lockUptime
|
||||
inflictedDmg = 0
|
||||
remainingEhp = srcEhp
|
||||
for i in range(int(uptimeAmountLimit)):
|
||||
timeAliveUnderFire = min(lockUptime, remainingEhp / tgtDps)
|
||||
timeAlive = lockDowntime + timeAliveUnderFire
|
||||
remainingEhp -= lockUptime * tgtDps
|
||||
inflictedDmg += timeAlive * srcWeaponDps
|
||||
inflictedDmg += max(0, timeAlive - DRONE_LOCK_TIME - 1) * srcDroneDps
|
||||
if remainingEhp <= 0:
|
||||
break
|
||||
return inflictedDmg
|
||||
|
||||
|
||||
class TgtScanRes2SrcDmgGetter(SrcDmgBaseGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
damage = self._calculateInflictedDamage(
|
||||
srcSigRadius=commonData['srcSigRadius'],
|
||||
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||
srcDroneDps=commonData['srcDpsDrone'],
|
||||
srcEhp=commonData['srcEhp'],
|
||||
tgtScanRes=x * commonData['tgtScanResMult'],
|
||||
tgtDps=miscParams['tgtDps'],
|
||||
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||
return damage
|
||||
|
||||
|
||||
class TgtDps2SrcDmgGetter(SrcDmgBaseGetter):
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
damage = self._calculateInflictedDamage(
|
||||
srcSigRadius=commonData['srcSigRadius'],
|
||||
srcWeaponDps=commonData['srcDpsWeapon'],
|
||||
srcDroneDps=commonData['srcDpsDrone'],
|
||||
srcEhp=commonData['srcEhp'],
|
||||
tgtScanRes=miscParams['tgtScanRes'] * commonData['tgtScanResMult'],
|
||||
tgtDps=x,
|
||||
uptimeAdjustment=miscParams['uptimeAdj'],
|
||||
uptimeAmountLimit=miscParams['uptimeAmtLimit'])
|
||||
return damage
|
||||
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
65
graphs/data/fitEcmBurstScanresDamps/graph.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
"""
|
||||
Disclaimer by kadesh: this graph was made to analyze my ECM burst + damp frig
|
||||
concept. I do not think it is useful for regular player, so it is disabled.
|
||||
Enable by setting config.experimentalFeatures = True.
|
||||
"""
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from graphs.data.base import FitGraph, XDef, YDef, Input, InputCheckbox
|
||||
from .getter import (
|
||||
TgtScanRes2TgtLockTimeGetter, TgtScanRes2TgtLockUptimeGetter,
|
||||
TgtScanRes2SrcDmgGetter, TgtDps2SrcDmgGetter)
|
||||
|
||||
|
||||
class FitEcmBurstScanresDampsGraph(FitGraph):
|
||||
|
||||
# UI stuff
|
||||
hidden = True
|
||||
internalName = 'ecmBurstScanresDamps'
|
||||
name = 'ECM Burst + Scanres Damps'
|
||||
xDefs = [
|
||||
XDef(handle='tgtDps', unit=None, label='Enemy DPS', mainInput=('tgtDps', None)),
|
||||
XDef(handle='tgtScanRes', unit='mm', label='Enemy scanres', mainInput=('tgtScanRes', 'mm'))]
|
||||
yDefs = [
|
||||
YDef(handle='srcDmg', unit=None, label='Damage inflicted'),
|
||||
YDef(handle='tgtLockTime', unit='s', label='Lock time'),
|
||||
YDef(handle='tgtLockUptime', unit='s', label='Lock uptime')]
|
||||
inputs = [
|
||||
Input(handle='tgtScanRes', unit='mm', label='Enemy scanres', iconID=74, defaultValue=700, defaultRange=(100, 1000)),
|
||||
Input(handle='tgtDps', unit=None, label='Enemy DPS', iconID=1432, defaultValue=200, defaultRange=(100, 600)),
|
||||
Input(handle='uptimeAdj', unit='s', label='Uptime adjustment', iconID=1392, defaultValue=1, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))]),
|
||||
Input(handle='uptimeAmtLimit', unit=None, label='Max amount of uptimes', iconID=1397, defaultValue=3, defaultRange=(None, None), conditions=[(None, ('srcDmg', None))])]
|
||||
checkboxes = [
|
||||
InputCheckbox(handle='applyDamps', label='Apply sensor dampeners', defaultValue=True),
|
||||
InputCheckbox(handle='applyDrones', label='Use drones', defaultValue=True, conditions=[(None, ('srcDmg', None))])]
|
||||
srcExtraCols = ('SigRadius', 'Damp ScanRes')
|
||||
|
||||
# Calculation stuff
|
||||
_limiters = {'tgtScanRes': lambda src, tgt: (1, math.inf)}
|
||||
_getters = {
|
||||
('tgtScanRes', 'tgtLockTime'): TgtScanRes2TgtLockTimeGetter,
|
||||
('tgtScanRes', 'tgtLockUptime'): TgtScanRes2TgtLockUptimeGetter,
|
||||
('tgtScanRes', 'srcDmg'): TgtScanRes2SrcDmgGetter,
|
||||
('tgtDps', 'srcDmg'): TgtDps2SrcDmgGetter}
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
import math
|
||||
|
||||
from graphs.calc import calculateMultiplier, calculateRangeFactor
|
||||
from eos.calc import calculateMultiplier, calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
@@ -37,33 +38,37 @@ class Distance2NeutingStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'energyNosferatuFalloff' in mod.item.effects and mod.getModifiedItemAttr('nosOverride'):
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('powerTransferAmount') / self.__getDuration(mod) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'doomsdayAOENeut' in mod.item.effects:
|
||||
neuts.append((
|
||||
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0))
|
||||
mod.falloff or 0, False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'entityEnergyNeutralizerFalloff' in drone.item.effects:
|
||||
neuts.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('energyNeutralizerAmount') / (drone.getModifiedItemAttr('energyNeutralizerDuration') / 1000) * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0),))
|
||||
math.inf, 0, True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityEnergyNeutralizer':
|
||||
nps = fighter.getModifiedItemAttr('fighterAbilityEnergyNeutralizerAmount') / (ability.cycleTime / 1000)
|
||||
neuts.append((
|
||||
nps * fighter.amount * resonance,
|
||||
math.inf, 0))
|
||||
math.inf, 0, True, False))
|
||||
return {'neuts': neuts}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
combinedStr = 0
|
||||
for strength, optimal, falloff in commonData['neuts']:
|
||||
for strength, optimal, falloff, needsLock, needsDcr in commonData['neuts']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
return combinedStr
|
||||
|
||||
@@ -84,28 +89,32 @@ class Distance2WebbingStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEWeb' in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteWebifierEntity' in drone.item.effects:
|
||||
webs.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('speedFactor') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||
webs.append((
|
||||
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amount * resonance,
|
||||
math.inf, 0, 'default'))
|
||||
math.inf, 0, 'default', True, False))
|
||||
return {'webs': webs}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['webs']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['webs']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -129,28 +138,32 @@ class Distance2EcmStrMaxGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
ecms.append((
|
||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0))
|
||||
mod.maxRange or 0, mod.falloff or 0, True, False))
|
||||
if 'doomsdayAOEECM' in mod.item.effects:
|
||||
ecms.append((
|
||||
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0))
|
||||
mod.falloff or 0, False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'entityECMFalloff' in drone.item.effects:
|
||||
ecms.extend(drone.amountActive * ((
|
||||
max(drone.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0),))
|
||||
math.inf, 0, True, True),))
|
||||
for fighter, ability in src.item.activeFighterAbilityIter():
|
||||
if ability.effect.name == 'fighterAbilityECM':
|
||||
ecms.append((
|
||||
max(fighter.getModifiedItemAttr(a) for a in self.ECM_ATTRS_FIGHTERS) * fighter.amount * resonance,
|
||||
math.inf, 0))
|
||||
math.inf, 0, True, False))
|
||||
return {'ecms': ecms}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
combinedStr = 0
|
||||
for strength, optimal, falloff in commonData['ecms']:
|
||||
for strength, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
return combinedStr
|
||||
|
||||
@@ -168,23 +181,27 @@ class Distance2DampStrLockRangeGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
damps.append((
|
||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEDamp' in mod.item.effects:
|
||||
damps.append((
|
||||
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteSensorDampEntity' in drone.item.effects:
|
||||
damps.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'damps': damps}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['damps']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['damps']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -205,23 +222,27 @@ class Distance2TdStrOptimalGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
tds.append((
|
||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOETrack' in mod.item.effects:
|
||||
tds.append((
|
||||
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'npcEntityWeaponDisruptor' in drone.item.effects:
|
||||
tds.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('maxRangeBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'tds': tds}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['tds']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tds']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
@@ -243,20 +264,24 @@ class Distance2GdStrRangeGetter(SmoothPointGetter):
|
||||
gds.append((
|
||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOETrack' in mod.item.effects:
|
||||
gds.append((
|
||||
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
|
||||
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
return {'gds': gds}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
velocityStrMults = {}
|
||||
timeStrMults = {}
|
||||
for velocityStr, timeStr, optimal, falloff, stackingGroup in commonData['gds']:
|
||||
for velocityStr, timeStr, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['gds']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
velocityStr *= rangeFactor
|
||||
timeStr *= rangeFactor
|
||||
@@ -281,23 +306,27 @@ class Distance2TpStrGetter(SmoothPointGetter):
|
||||
if effectName in mod.item.effects:
|
||||
tps.append((
|
||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
|
||||
if 'doomsdayAOEPaint' in mod.item.effects:
|
||||
tps.append((
|
||||
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
mod.falloff or 0, 'default', False, False))
|
||||
for drone in src.item.activeDronesIter():
|
||||
if 'remoteTargetPaintEntity' in drone.item.effects:
|
||||
tps.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('signatureRadiusBonus') * resonance,
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
math.inf, 0, 'default', True, True),))
|
||||
return {'tps': tps}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['tps']:
|
||||
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tps']:
|
||||
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
|
||||
continue
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -18,23 +18,32 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from eos.calc import calculateRangeFactor
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from graphs.calc import checkLockRange, checkDroneControlRange
|
||||
|
||||
|
||||
def getApplicationPerKey(src, distance):
|
||||
inLockRange = checkLockRange(src=src, distance=distance)
|
||||
inDroneRange = checkDroneControlRange(src=src, distance=distance)
|
||||
applicationMap = {}
|
||||
for mod in src.item.activeModulesIter():
|
||||
if not mod.isRemoteRepping():
|
||||
continue
|
||||
applicationMap[mod] = 1 if distance is None else calculateRangeFactor(
|
||||
srcOptimalRange=mod.maxRange or 0,
|
||||
srcFalloffRange=mod.falloff or 0,
|
||||
distance=distance)
|
||||
if not inLockRange:
|
||||
applicationMap[mod] = 0
|
||||
else:
|
||||
applicationMap[mod] = calculateRangeFactor(
|
||||
srcOptimalRange=mod.maxRange or 0,
|
||||
srcFalloffRange=mod.falloff or 0,
|
||||
distance=distance)
|
||||
for drone in src.item.activeDronesIter():
|
||||
if not drone.isRemoteRepping():
|
||||
continue
|
||||
applicationMap[drone] = 1 if distance is None or distance <= src.item.extraAttributes['droneControlRange'] else 0
|
||||
if not inLockRange or not inDroneRange:
|
||||
applicationMap[drone] = 0
|
||||
else:
|
||||
applicationMap[drone] = 1
|
||||
# Ensure consistent results - round off a little to avoid float errors
|
||||
for k, v in applicationMap.items():
|
||||
applicationMap[k] = floatUnerr(v)
|
||||
|
||||
@@ -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()
|
||||
@@ -116,9 +118,7 @@ class GraphFrame(AuxiliaryFrame):
|
||||
self.SetMinSize(newSize)
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
@@ -152,10 +152,10 @@ class BaseWrapperList(gui.display.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
self.removeWrappers(self.getSelectedWrappers())
|
||||
event.Skip()
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinAdditionPanes.boosterView import BoosterView
|
||||
from gui.builtinAdditionPanes.cargoView import CargoView
|
||||
@@ -35,9 +36,10 @@ from gui.toggle_panel import TogglePanel
|
||||
|
||||
class AdditionsPane(TogglePanel):
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, mainFrame):
|
||||
|
||||
TogglePanel.__init__(self, parent, force_layout=1)
|
||||
self.mainFrame = mainFrame
|
||||
|
||||
self.SetLabel("Additions")
|
||||
pane = self.GetContentPanel()
|
||||
@@ -45,7 +47,7 @@ class AdditionsPane(TogglePanel):
|
||||
baseSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
pane.SetSizer(baseSizer)
|
||||
|
||||
self.notebook = ChromeNotebook(pane, False)
|
||||
self.notebook = ChromeNotebook(pane, can_add=False, tabWidthMode=1)
|
||||
self.notebook.SetMinSize((-1, 1000))
|
||||
|
||||
baseSizer.Add(self.notebook, 1, wx.EXPAND)
|
||||
@@ -83,6 +85,9 @@ class AdditionsPane(TogglePanel):
|
||||
self.notes = NotesView(self.notebook)
|
||||
self.notebook.AddPage(self.notes, "Notes", image=notesImg, closeable=False)
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged)
|
||||
self.mainFrame.Bind(GE.FIT_NOTES_CHANGED, self.OnNotesChanged)
|
||||
|
||||
self.notebook.SetSelection(0)
|
||||
|
||||
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
|
||||
@@ -106,3 +111,25 @@ class AdditionsPane(TogglePanel):
|
||||
self.parent.SetSashInvisible(False)
|
||||
self.parent.SetMinimumPaneSize(200)
|
||||
self.parent.SetSashPosition(self.old_pos, True)
|
||||
|
||||
def OnFitChanged(self, event):
|
||||
event.Skip()
|
||||
activeFitID = self.mainFrame.getActiveFit()
|
||||
if activeFitID is not None and activeFitID not in event.fitIDs:
|
||||
return
|
||||
self.updateExtraText()
|
||||
|
||||
def OnNotesChanged(self, event):
|
||||
event.Skip()
|
||||
self.updateExtraText()
|
||||
|
||||
def updateExtraText(self):
|
||||
refresh = False
|
||||
for i in range(self.notebook.GetPageCount()):
|
||||
page = self.notebook.GetPage(i)
|
||||
if hasattr(page, 'getTabExtraText'):
|
||||
refresh = True
|
||||
self.notebook.SetPageTitleExtra(i, page.getTabExtraText() or '', refresh=False)
|
||||
if refresh:
|
||||
self.notebook.tabs_container.AdjustTabsSize()
|
||||
self.notebook.Refresh()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import io
|
||||
import os.path
|
||||
import zipfile
|
||||
from collections import OrderedDict
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -32,15 +33,12 @@ pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class BitmapLoader:
|
||||
# try:
|
||||
# archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r')
|
||||
# logging.info("Using zipped image files.")
|
||||
# except (IOError, TypeError):
|
||||
# logging.info("Using local image files.")
|
||||
# archive = None
|
||||
|
||||
pyfalog.info("Using local image files.")
|
||||
archive = None
|
||||
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
|
||||
@@ -131,8 +129,8 @@ class BitmapLoader:
|
||||
|
||||
try:
|
||||
img_data = cls.archive.read(path)
|
||||
sbuf = io.StringIO(img_data)
|
||||
return wx.ImageFromStream(sbuf)
|
||||
bbuf = io.BytesIO(img_data)
|
||||
return wx.Image(bbuf)
|
||||
except KeyError:
|
||||
pyfalog.warning("Missing icon file from zip: {0}".format(path))
|
||||
else:
|
||||
|
||||
@@ -88,12 +88,12 @@ class BoosterView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
boosters = self.getSelectedBoosters()
|
||||
self.removeBoosters(boosters)
|
||||
event.Skip()
|
||||
@@ -226,3 +226,23 @@ class BoosterView(d.Display):
|
||||
continue
|
||||
boosters.append(booster)
|
||||
return boosters
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active boosters
|
||||
if opt == 1:
|
||||
amount = len([b for b in fit.boosters if b.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of boosters
|
||||
elif opt == 2:
|
||||
amount = len(fit.boosters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -104,12 +104,12 @@ class CargoView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
cargos = self.getSelectedCargos()
|
||||
self.removeCargos(cargos)
|
||||
event.Skip()
|
||||
@@ -214,3 +214,19 @@ class CargoView(d.Display):
|
||||
continue
|
||||
cargos.append(cargo)
|
||||
return cargos
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Total amount of cargo items
|
||||
if opt in (1, 2):
|
||||
amount = len(fit.cargo)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -102,12 +102,12 @@ class CommandView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
commandFits = self.getSelectedCommandFits()
|
||||
self.removeCommandFits(commandFits)
|
||||
event.Skip()
|
||||
@@ -247,3 +247,27 @@ class CommandView(d.Display):
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitsCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
commandFitIDs=fitIDs))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active command fits
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for commandFit in fit.commandFits:
|
||||
info = commandFit.getCommandInfo(fitID)
|
||||
if info is not None and info.active:
|
||||
amount += 1
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of command fits
|
||||
elif opt == 2:
|
||||
amount = len(fit.commandFits)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -120,12 +120,12 @@ class DroneView(Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
drones = self.getSelectedDrones()
|
||||
self.removeDroneStacks(drones)
|
||||
event.Skip()
|
||||
@@ -260,7 +260,7 @@ class DroneView(Display):
|
||||
drone = self.drones[row]
|
||||
except IndexError:
|
||||
return
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if event.GetModifiers() == wx.MOD_ALT:
|
||||
self.removeDroneStacks([drone])
|
||||
else:
|
||||
self.removeDrone(drone)
|
||||
@@ -337,3 +337,27 @@ class DroneView(Display):
|
||||
continue
|
||||
drones.append(drone)
|
||||
return drones
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active drones
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for droneStack in fit.drones:
|
||||
amount += droneStack.amountActive
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of drones
|
||||
elif opt == 2:
|
||||
amount = 0
|
||||
for droneStack in fit.drones:
|
||||
amount += droneStack.amount
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -117,6 +117,26 @@ class FighterView(wx.Panel):
|
||||
|
||||
self.Refresh()
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active fighter squads
|
||||
if opt == 1:
|
||||
amount = len([f for f in fit.fighters if f.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of fighter squads
|
||||
elif opt == 2:
|
||||
amount = len(fit.fighters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class FighterDisplay(d.Display):
|
||||
|
||||
@@ -185,12 +205,12 @@ class FighterDisplay(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
fighters = self.getSelectedFighters()
|
||||
self.removeFighters(fighters)
|
||||
event.Skip()
|
||||
@@ -295,12 +315,11 @@ class FighterDisplay(d.Display):
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
mstate = wx.GetMouseState()
|
||||
try:
|
||||
fighter = self.fighters[row]
|
||||
except IndexError:
|
||||
return
|
||||
if mstate.GetModifiers() == wx.MOD_ALT:
|
||||
if event.GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(self.original, fighter)
|
||||
else:
|
||||
fighters = [fighter]
|
||||
|
||||
@@ -101,6 +101,25 @@ class ImplantView(wx.Panel):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeImplantLocationCommand(
|
||||
fitID=fitID, source=ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active implants
|
||||
if opt == 1:
|
||||
amount = len([i for i in fit.appliedImplants if i.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of implants
|
||||
elif opt == 2:
|
||||
amount = len(fit.appliedImplants)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
class ImplantDisplay(d.Display):
|
||||
|
||||
@@ -143,12 +162,12 @@ class ImplantDisplay(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
implants = self.getSelectedImplants()
|
||||
self.removeImplants(implants)
|
||||
event.Skip()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from service.fit import Fit
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class NotesView(wx.Panel):
|
||||
@@ -14,14 +15,14 @@ class NotesView(wx.Panel):
|
||||
self.lastFitId = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.editNotes = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.BORDER_NONE, )
|
||||
mainSizer.Add(self.editNotes, 1, wx.EXPAND)
|
||||
self.editNotes = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.BORDER_NONE)
|
||||
mainSizer.Add(self.editNotes, 1, wx.EXPAND | wx.ALL, 10)
|
||||
self.SetSizer(mainSizer)
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.Bind(wx.EVT_TEXT, self.onText)
|
||||
self.editNotes.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
||||
self.saveTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.delayedSave, self.saveTimer)
|
||||
self.changeTimer = wx.Timer(self)
|
||||
self.Bind(wx.EVT_TIMER, self.delayedSave, self.changeTimer)
|
||||
|
||||
def OnKeyDown(self, event):
|
||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
@@ -38,7 +39,7 @@ class NotesView(wx.Panel):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(activeFitID)
|
||||
|
||||
self.saveTimer.Stop() # cancel any pending timers
|
||||
self.changeTimer.Stop() # cancel any pending timers
|
||||
|
||||
self.Parent.Parent.DisablePage(self, not fit or fit.isStructure)
|
||||
|
||||
@@ -51,13 +52,32 @@ class NotesView(wx.Panel):
|
||||
return
|
||||
elif activeFitID != self.lastFitId:
|
||||
self.lastFitId = activeFitID
|
||||
self.editNotes.SetValue(fit.notes or "")
|
||||
self.editNotes.ChangeValue(fit.notes or "")
|
||||
wx.PostEvent(self.mainFrame, GE.FitNotesChanged())
|
||||
|
||||
def onText(self, event):
|
||||
# delay the save so we're not writing to sqlite on every keystroke
|
||||
self.saveTimer.Stop() # cancel the existing timer
|
||||
self.saveTimer.Start(1000, True)
|
||||
self.changeTimer.Stop() # cancel the existing timer
|
||||
self.changeTimer.Start(1000, True)
|
||||
|
||||
def delayedSave(self, event):
|
||||
event.Skip()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.editNotes(self.lastFitId, self.editNotes.GetValue())
|
||||
wx.PostEvent(self.mainFrame, GE.FitNotesChanged())
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active implants
|
||||
if opt in (1, 2):
|
||||
amount = len(self.editNotes.GetValue())
|
||||
return ' ({})'.format(formatAmount(amount, 2, 0, 3)) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -27,6 +27,7 @@ import gui.builtinAdditionPanes.droneView
|
||||
import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from eos.const import FittingModuleState
|
||||
from eos.saveddata.drone import Drone as EosDrone
|
||||
from eos.saveddata.fighter import Fighter as EosFighter
|
||||
from eos.saveddata.fit import Fit as EosFit
|
||||
@@ -74,7 +75,8 @@ class ProjectedView(d.Display):
|
||||
'Ammo Icon',
|
||||
'Base Icon',
|
||||
'Base Name',
|
||||
'Ammo']
|
||||
'Ammo',
|
||||
'Projection Range']
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
@@ -127,12 +129,12 @@ class ProjectedView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
items=self.getSelectedProjectors(),
|
||||
@@ -265,7 +267,7 @@ class ProjectedView(d.Display):
|
||||
selection = self.getSelectedProjectors()
|
||||
if mainItem not in selection:
|
||||
selection = [mainItem]
|
||||
modPressed = wx.GetMouseState().GetModifiers() == wx.MOD_ALT
|
||||
modPressed = event.GetModifiers() == wx.MOD_ALT
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if isinstance(mainItem, EosModule) and modPressed:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
@@ -340,7 +342,7 @@ class ProjectedView(d.Display):
|
||||
if mainItem is None:
|
||||
return
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
modPressed = wx.GetMouseState().GetModifiers() == wx.MOD_ALT
|
||||
modPressed = event.GetModifiers() == wx.MOD_ALT
|
||||
if isinstance(mainItem, EosFit):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=[mainItem], amount=math.inf if modPressed else 1))
|
||||
@@ -396,3 +398,34 @@ class ProjectedView(d.Display):
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
projectedFitIDs=fitIDs,
|
||||
amount=1))
|
||||
|
||||
def getTabExtraText(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return None
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is None:
|
||||
return None
|
||||
opt = sFit.serviceFittingOptions["additionsLabels"]
|
||||
# Amount of active projected items
|
||||
if opt == 1:
|
||||
amount = 0
|
||||
for projectedFit in fit.projectedFits:
|
||||
info = projectedFit.getProjectionInfo(fitID)
|
||||
if info is not None and info.active:
|
||||
amount += 1
|
||||
amount += len([m for m in fit.projectedModules if m.state > FittingModuleState.OFFLINE])
|
||||
amount += len([d for d in fit.projectedDrones if d.amountActive > 0])
|
||||
amount += len([f for f in fit.projectedFighters if f.active])
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
# Total amount of projected items
|
||||
elif opt == 2:
|
||||
amount = 0
|
||||
amount += len(fit.projectedFits)
|
||||
amount += len(fit.projectedModules)
|
||||
amount += len(fit.projectedDrones)
|
||||
amount += len(fit.projectedFighters)
|
||||
return ' ({})'.format(amount) if amount else None
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -22,6 +22,7 @@ from gui.builtinContextMenus import shipJump
|
||||
# Generic item manipulations
|
||||
from gui.builtinContextMenus import itemRemove
|
||||
from gui.builtinContextMenus import itemAmountChange
|
||||
from gui.builtinContextMenus import itemProjectionRange
|
||||
from gui.builtinContextMenus import droneSplitStack
|
||||
from gui.builtinContextMenus import itemVariationChange
|
||||
from gui.builtinContextMenus import moduleMutations
|
||||
@@ -44,8 +45,10 @@ from gui.builtinContextMenus import damagePatternChange
|
||||
from gui.builtinContextMenus import factorReload
|
||||
from gui.builtinContextMenus.targetProfile import switcher
|
||||
# Graph extra options
|
||||
from gui.builtinContextMenus import graphDmgIgnoreResists
|
||||
from gui.builtinContextMenus import graphDmgApplyProjected
|
||||
from gui.builtinContextMenus import graphDmgIgnoreResists
|
||||
from gui.builtinContextMenus import graphLockRange
|
||||
from gui.builtinContextMenus import graphDroneControlRange
|
||||
from gui.builtinContextMenus import graphDmgDroneMode
|
||||
# Additions panel menus
|
||||
from gui.builtinContextMenus import additionsExportSelection
|
||||
|
||||
@@ -69,7 +69,7 @@ class FighterAbilities(ContextMenuCombined):
|
||||
command = cmd.GuiToggleLocalFighterAbilityStateCommand
|
||||
if self.fighter in container:
|
||||
mainPosition = container.index(self.fighter)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(container, self.fighter)
|
||||
else:
|
||||
fighters = self.selection
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.builtinViews.emptyView import BlankPage
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
|
||||
30
gui/builtinContextMenus/graphDroneControlRange.py
Normal file
30
gui/builtinContextMenus/graphDroneControlRange.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
class GraphIgnoreDcrMenu(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = GraphSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
return srcContext in ('dmgStatsGraph', 'remoteRepsGraph', 'ewarStatsGraph')
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Ignore Drone Control Range'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
self.settings.set('ignoreDCR', not self.settings.get('ignoreDCR'))
|
||||
wx.PostEvent(self.mainFrame, GE.GraphOptionChanged())
|
||||
|
||||
def isChecked(self, i):
|
||||
return self.settings.get('ignoreDCR')
|
||||
|
||||
|
||||
GraphIgnoreDcrMenu.register()
|
||||
30
gui/builtinContextMenus/graphLockRange.py
Normal file
30
gui/builtinContextMenus/graphLockRange.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.settings import GraphSettings
|
||||
|
||||
|
||||
class GraphIgnoreLockRangeMenu(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = GraphSettings.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext):
|
||||
return srcContext in ('dmgStatsGraph', 'remoteRepsGraph', 'ewarStatsGraph')
|
||||
|
||||
def getText(self, callingWindow, itmContext):
|
||||
return 'Ignore Lock Range'
|
||||
|
||||
def activate(self, callingWindow, fullContext, i):
|
||||
self.settings.set('ignoreLockRange', not self.settings.get('ignoreLockRange'))
|
||||
wx.PostEvent(self.mainFrame, GE.GraphOptionChanged())
|
||||
|
||||
def isChecked(self, i):
|
||||
return self.settings.get('ignoreLockRange')
|
||||
|
||||
|
||||
GraphIgnoreLockRangeMenu.register()
|
||||
125
gui/builtinContextMenus/itemProjectionRange.py
Normal file
125
gui/builtinContextMenus/itemProjectionRange.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.fighter import Fighter as EosFighter
|
||||
from eos.saveddata.fit import Fit as EosFit
|
||||
from eos.saveddata.module import Module as EosModule
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarFighters, getSimilarModPositions
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class ChangeItemProjectionRange(ContextMenuCombined):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if srcContext not in ('projectedFit', 'projectedModule', 'projectedDrone', 'projectedFighter'):
|
||||
return False
|
||||
if mainItem is None:
|
||||
return False
|
||||
if getattr(mainItem, 'isExclusiveSystemEffect', False):
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return 'Change {} Range'.format(itmContext)
|
||||
|
||||
def activate(self, callingWindow, fullContext, mainItem, selection, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if isinstance(mainItem, EosFit):
|
||||
try:
|
||||
value = mainItem.getProjectionInfo(fitID).projectionRange
|
||||
except AttributeError:
|
||||
return
|
||||
else:
|
||||
value = mainItem.projectionRange
|
||||
if value is not None:
|
||||
value /= 1000
|
||||
with RangeChanger(self.mainFrame, value) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
|
||||
if cleanInput:
|
||||
try:
|
||||
cleanInputFloat = float(cleanInput)
|
||||
except ValueError:
|
||||
return
|
||||
newRange = cleanInputFloat * 1000
|
||||
else:
|
||||
newRange = None
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
items = selection
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
if isinstance(mainItem, EosModule):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedItemsProjectionRangeCommand(
|
||||
fitID=fitID, items=items, projectionRange=newRange))
|
||||
|
||||
|
||||
ChangeItemProjectionRange.register()
|
||||
|
||||
|
||||
class RangeChanger(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, value):
|
||||
super().__init__(parent, title='Change Projection Range', style=wx.DEFAULT_DIALOG_STYLE)
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, 'New Range, km:')
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
if value is None:
|
||||
value = ''
|
||||
else:
|
||||
if value == int(value):
|
||||
value = int(value)
|
||||
value = str(value)
|
||||
self.input.SetValue(value)
|
||||
self.input.SelectAll()
|
||||
|
||||
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
|
||||
|
||||
bSizer3 = wx.BoxSizer(wx.VERTICAL)
|
||||
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
|
||||
|
||||
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
|
||||
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
|
||||
|
||||
self.input.SetFocus()
|
||||
self.input.Bind(wx.EVT_CHAR, self.onChar)
|
||||
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
|
||||
self.SetSizer(bSizer1)
|
||||
self.CenterOnParent()
|
||||
self.Fit()
|
||||
|
||||
def processEnter(self, evt):
|
||||
self.EndModal(wx.ID_OK)
|
||||
|
||||
# checks to make sure it's valid number
|
||||
@staticmethod
|
||||
def onChar(event):
|
||||
key = event.GetKeyCode()
|
||||
|
||||
acceptable_characters = '1234567890.'
|
||||
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
|
||||
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
|
||||
event.Skip()
|
||||
return
|
||||
else:
|
||||
return False
|
||||
@@ -65,7 +65,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
def __handleModule(self, callingWindow, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.modules, mainItem)
|
||||
else:
|
||||
positions = []
|
||||
@@ -88,7 +88,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
def __handleFighter(self, callingWindow, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.fighters, mainItem)
|
||||
else:
|
||||
fighters = selection
|
||||
@@ -131,7 +131,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosModule):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
@@ -143,7 +143,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
else:
|
||||
|
||||
@@ -48,10 +48,9 @@ class ItemStats(ContextMenuSingle):
|
||||
if srcContext == "fittingModule" and stuff.isEmpty:
|
||||
return
|
||||
|
||||
mstate = wx.GetMouseState()
|
||||
reuse = False
|
||||
|
||||
if mstate.GetModifiers() == wx.MOD_SHIFT:
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_SHIFT:
|
||||
reuse = True
|
||||
|
||||
if self.mainFrame.GetActiveStatsWindow() is None and reuse:
|
||||
|
||||
@@ -43,6 +43,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
self.mainItem = mainItem
|
||||
self.selection = selection
|
||||
self.srcContext = srcContext
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
@@ -55,9 +56,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
def get_metalevel(x):
|
||||
if 'metaLevel' not in x.attributes:
|
||||
return 0
|
||||
return x.attributes['metaLevel'].value
|
||||
return x.metaLevel or 0
|
||||
|
||||
def get_metagroup(x):
|
||||
# We want deadspace before officer mods
|
||||
@@ -126,7 +125,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
self.moduleLookup[id] = item, context
|
||||
m.Append(mitem)
|
||||
mitem.Enable(fit.canFit(item))
|
||||
mitem.Enable(self.srcContext in ('projectedModule', 'projectedDrone', 'projectedFighter') or fit.canFit(item))
|
||||
|
||||
return m
|
||||
|
||||
@@ -153,7 +152,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.modules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
@@ -192,7 +191,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.fighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
@@ -245,7 +244,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleProjectedModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
positions = getSimilarModPositions(fit.projectedModules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
@@ -282,7 +281,7 @@ class ChangeItemToVariation(ContextMenuCombined):
|
||||
def __handleProjectedFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
if wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL):
|
||||
fighters = getSimilarFighters(fit.projectedFighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
|
||||
@@ -226,9 +226,8 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
mstate = wx.GetMouseState()
|
||||
# Switch in selection or all modules, depending on modifier key state and settings
|
||||
switchAll = sFit.serviceFittingOptions['ammoChangeAll'] is not (mstate.GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL))
|
||||
switchAll = sFit.serviceFittingOptions['ammoChangeAll'] is not (wx.GetMouseState().GetModifiers() in (wx.MOD_ALT, wx.MOD_CONTROL))
|
||||
if switchAll:
|
||||
if self.srcContext == 'fittingModule':
|
||||
command = cmd.GuiChangeLocalModuleChargesCommand
|
||||
|
||||
@@ -38,6 +38,7 @@ class ItemAffectedBy(wx.Panel):
|
||||
|
||||
def __init__(self, parent, stuff, item):
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
self.stuff = stuff
|
||||
self.item = item
|
||||
|
||||
@@ -52,6 +53,7 @@ class ItemAffectedBy(wx.Panel):
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.affectedBy = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER)
|
||||
self.affectedBy.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
|
||||
mainSizer.Add(self.affectedBy, 1, wx.ALL | wx.EXPAND, 0)
|
||||
|
||||
self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
|
||||
@@ -21,6 +21,8 @@ class ItemParams(wx.Panel):
|
||||
def __init__(self, parent, stuff, item, context=None):
|
||||
# Had to manually set the size here, otherwise column widths couldn't be calculated correctly. See #1878
|
||||
wx.Panel.__init__(self, parent, size=(1000, 1000))
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.paramList = wx.lib.agw.hypertreelist.HyperTreeList(self, wx.ID_ANY, agwStyle=wx.TR_HIDE_ROOT | wx.TR_NO_LINES | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_HAS_BUTTONS)
|
||||
|
||||
@@ -9,7 +9,7 @@ from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
|
||||
def defaultSort(item):
|
||||
return (item.attributes['metaLevel'].value if 'metaLevel' in item.attributes else 0, item.name)
|
||||
return (item.metaLevel or 0, item.name)
|
||||
|
||||
|
||||
class ItemCompare(wx.Panel):
|
||||
@@ -19,6 +19,7 @@ class ItemCompare(wx.Panel):
|
||||
sPrice.getPrices(items, self.UpdateList, fetchTimeout=90)
|
||||
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.paramList = AutoListCtrl(self, wx.ID_ANY,
|
||||
|
||||
@@ -21,6 +21,7 @@ class ItemMutatorPanel(wx.Panel):
|
||||
def __init__(self, parent, mod):
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.stuff = mod
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -72,6 +73,7 @@ class ItemMutatorList(wx.ScrolledWindow):
|
||||
self.SetScrollRate(0, 15)
|
||||
self.carryingFitID = gui.mainFrame.MainFrame.getInstance().getActiveFit()
|
||||
self.initialMutations = {}
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
|
||||
self.mod = mod
|
||||
self.timer = None
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ class ItemView(Display):
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = self.metaMap.get(metagrpid)
|
||||
metalvl = self.metalvls.get(item.ID, 0)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
@@ -237,11 +237,6 @@ class ItemView(Display):
|
||||
|
||||
def populate(self, items):
|
||||
if len(items) > 0:
|
||||
# Get dictionary with meta level attribute
|
||||
sAttr = Attribute.getInstance()
|
||||
attrs = sAttr.getAttributeInfo("metaLevel")
|
||||
sMkt = self.sMkt
|
||||
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
||||
# Clear selection
|
||||
self.unselectAll()
|
||||
# Perform sorting, using item's meta levels besides other stuff
|
||||
@@ -254,11 +249,6 @@ class ItemView(Display):
|
||||
|
||||
def refresh(self, items):
|
||||
if len(items) > 1:
|
||||
# Get dictionary with meta level attribute
|
||||
sAttr = Attribute.getInstance()
|
||||
attrs = sAttr.getAttributeInfo("metaLevel")
|
||||
sMkt = self.sMkt
|
||||
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
||||
# Re-sort stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
|
||||
@@ -19,8 +19,7 @@ class PFContextMenuPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
|
||||
"Disabling context menus can improve responsiveness.",
|
||||
|
||||
@@ -19,7 +19,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, "(Cannot be changed while pyfa is running. Set via command line switches.)",
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
|
||||
@@ -35,7 +35,7 @@ class PFFittingEnginePref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -24,8 +24,7 @@ class PFEsiPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -1,496 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.preferenceView import PreferenceView
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Class PFGaugePref
|
||||
###########################################################################
|
||||
|
||||
|
||||
class PFGaugePreview(wx.Window):
|
||||
def __init__(self, parent, id=wx.ID_ANY, value=0, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
|
||||
wx.Window.__init__(self, parent, id, pos=pos, size=size, style=style)
|
||||
|
||||
self.value = float(value)
|
||||
self.oldValue = self.value
|
||||
|
||||
self.percS = 0
|
||||
self.percE = 0
|
||||
|
||||
self.animate = True
|
||||
self.animDir = 1
|
||||
self._fractionDigits = 2
|
||||
|
||||
self.colorS = wx.Colour(0, 0, 0, 255)
|
||||
self.colorE = wx.Colour(0, 0, 0, 255)
|
||||
self.gradientStart = 0
|
||||
|
||||
self.bkColor = wx.Colour(0, 0, 0, 255)
|
||||
self.SetMinSize((100, -1))
|
||||
|
||||
self.font = wx.FontFromPixelSize((0, 13), wx.SWISS, wx.NORMAL, wx.NORMAL, False)
|
||||
|
||||
self.timerID = wx.NewId()
|
||||
self.timer = wx.Timer(self, self.timerID)
|
||||
self.timerInterval = 20
|
||||
|
||||
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||
self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter)
|
||||
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave)
|
||||
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
||||
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBk)
|
||||
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
|
||||
|
||||
def OnEraseBk(self, event):
|
||||
pass
|
||||
|
||||
def OnTimer(self, event):
|
||||
if event.GetId() == self.timerID:
|
||||
self.value += self.animDir
|
||||
if self.value > 100:
|
||||
self.value = 100
|
||||
self.animDir = -1
|
||||
if self.value < 0:
|
||||
self.value = 0
|
||||
self.animDir = 1
|
||||
self.Refresh()
|
||||
|
||||
def OnWindowEnter(self, event):
|
||||
if not self.animate:
|
||||
return
|
||||
self.oldValue = self.value
|
||||
if self.timer.IsRunning():
|
||||
self.timer.Stop()
|
||||
self.timer.Start(self.timerInterval)
|
||||
event.Skip()
|
||||
|
||||
def OnWindowLeave(self, event):
|
||||
if not self.animate:
|
||||
return
|
||||
if self.timer.IsRunning():
|
||||
self.timer.Stop()
|
||||
self.value = self.oldValue
|
||||
self.Refresh()
|
||||
event.Skip()
|
||||
|
||||
def CanAnimate(self, anim=True):
|
||||
self.animate = anim
|
||||
if self.timer.IsRunning():
|
||||
self.timer.Stop()
|
||||
self.value = self.oldValue
|
||||
self.Refresh()
|
||||
|
||||
def SetGradientStart(self, value):
|
||||
self.gradientStart = value
|
||||
self.Refresh()
|
||||
|
||||
def SetColour(self, colorS, colorE):
|
||||
self.colorS = colorS
|
||||
self.colorE = colorE
|
||||
|
||||
self.Refresh()
|
||||
|
||||
def SetValue(self, value):
|
||||
self.value = min(max(value, 0), 100)
|
||||
self.Refresh()
|
||||
|
||||
def SetPercentages(self, start, end):
|
||||
self.percS = start
|
||||
self.percE = end
|
||||
self.Refresh()
|
||||
|
||||
def OnPaint(self, event):
|
||||
pass
|
||||
# rect = self.GetClientRect()
|
||||
# dc = wx.AutoBufferedPaintDC(self)
|
||||
# dc.SetBackground(wx.Brush(self.bkColor))
|
||||
# dc.Clear()
|
||||
#
|
||||
# value = float(self.value)
|
||||
# if self.percS >= 100:
|
||||
# w = rect.width
|
||||
# else:
|
||||
# w = rect.width * (float(value) / 100)
|
||||
# r = copy.copy(rect)
|
||||
# r.width = w
|
||||
#
|
||||
# color = CalculateTransitionColor(self.colorS, self.colorE, float(value) / 100)
|
||||
# if self.gradientStart > 0:
|
||||
# gcolor = color.BrightenColor(color, float(self.gradientStart) / 100)
|
||||
# gMid = color.BrightenColor(color, float(self.gradientStart / 2) / 100)
|
||||
# else:
|
||||
# gcolor = color.DarkenColor(color, float(-self.gradientStart) / 100)
|
||||
# gMid = color.DarkenColor(color, float(-self.gradientStart / 2) / 100)
|
||||
#
|
||||
# gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor)
|
||||
# dc.DrawBitmap(gBmp, 0, 0)
|
||||
# dc.SetFont(self.font)
|
||||
#
|
||||
# r = copy.copy(rect)
|
||||
# r.left += 1
|
||||
# r.top += 1
|
||||
#
|
||||
# formatStr = "{0:." + str(self._fractionDigits) + "f}%"
|
||||
# value = (self.percE - self.percS) * value / (self.percE - self.percS)
|
||||
# value = self.percS + (self.percE - self.percS) * value / 100
|
||||
#
|
||||
# dc.SetTextForeground(wx.Colour(80, 80, 80))
|
||||
# dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER)
|
||||
#
|
||||
# dc.SetTextForeground(wx.Colour(255, 255, 255))
|
||||
# dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER)
|
||||
|
||||
|
||||
class PFGaugePref(PreferenceView):
|
||||
title = "Pyfa Gauge Theme"
|
||||
|
||||
def populatePanel(self, panel):
|
||||
|
||||
self.InitDefaultColours()
|
||||
|
||||
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
gSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.st0100 = wx.StaticText(panel, wx.ID_ANY, "0 - 100", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT)
|
||||
self.st0100.Wrap(-1)
|
||||
gSizer1.Add(self.st0100, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp0100S = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer1.Add(self.cp0100S, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp0100E = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer1.Add(self.cp0100E, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.gauge0100S = PFGaugePreview(panel, wx.ID_ANY, 33, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer1.Add(self.gauge0100S, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge0100M = PFGaugePreview(panel, wx.ID_ANY, 66, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer1.Add(self.gauge0100M, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge0100E = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer1.Add(self.gauge0100E, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5)
|
||||
|
||||
mainSizer.Add(gSizer1, 0, wx.EXPAND, 5)
|
||||
|
||||
gSizer2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.st100101 = wx.StaticText(panel, wx.ID_ANY, "100 - 101", wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.ALIGN_RIGHT)
|
||||
self.st100101.Wrap(-1)
|
||||
gSizer2.Add(self.st100101, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp100101S = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer2.Add(self.cp100101S, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp100101E = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer2.Add(self.cp100101E, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.gauge100101S = PFGaugePreview(panel, wx.ID_ANY, 33, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer2.Add(self.gauge100101S, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge100101M = PFGaugePreview(panel, wx.ID_ANY, 66, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer2.Add(self.gauge100101M, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge100101E = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer2.Add(self.gauge100101E, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5)
|
||||
|
||||
mainSizer.Add(gSizer2, 0, wx.EXPAND, 5)
|
||||
|
||||
gSizer3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.st101103 = wx.StaticText(panel, wx.ID_ANY, "101 - 103", wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.ALIGN_RIGHT)
|
||||
self.st101103.Wrap(-1)
|
||||
gSizer3.Add(self.st101103, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp101103S = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer3.Add(self.cp101103S, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp101103E = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer3.Add(self.cp101103E, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.gauge101103S = PFGaugePreview(panel, wx.ID_ANY, 33, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer3.Add(self.gauge101103S, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge101103M = PFGaugePreview(panel, wx.ID_ANY, 66, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer3.Add(self.gauge101103M, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge101103E = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer3.Add(self.gauge101103E, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5)
|
||||
|
||||
mainSizer.Add(gSizer3, 0, wx.EXPAND, 5)
|
||||
|
||||
gSizer4 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.st103105 = wx.StaticText(panel, wx.ID_ANY, "103 - 105", wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.ALIGN_RIGHT)
|
||||
self.st103105.Wrap(-1)
|
||||
gSizer4.Add(self.st103105, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp103105S = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer4.Add(self.cp103105S, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.cp103105E = wx.ColourPickerCtrl(panel, wx.ID_ANY, wx.BLACK, wx.DefaultPosition, wx.DefaultSize,
|
||||
wx.CLRP_DEFAULT_STYLE | wx.CLRP_SHOW_LABEL)
|
||||
gSizer4.Add(self.cp103105E, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.gauge103105S = PFGaugePreview(panel, wx.ID_ANY, 33, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer4.Add(self.gauge103105S, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge103105M = PFGaugePreview(panel, wx.ID_ANY, 66, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer4.Add(self.gauge103105M, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5)
|
||||
|
||||
self.gauge103105E = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, wx.SIMPLE_BORDER)
|
||||
gSizer4.Add(self.gauge103105E, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5)
|
||||
|
||||
mainSizer.Add(gSizer4, 0, wx.EXPAND, 5)
|
||||
|
||||
footerSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.sl1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
footerSizer.Add(self.sl1, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
previewSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.wndPreview0100 = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
previewSizer.Add(self.wndPreview0100, 1, wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.wndPreview100101 = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
previewSizer.Add(self.wndPreview100101, 1, wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.wndPreview101103 = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
previewSizer.Add(self.wndPreview101103, 1, wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.wndPreview103105 = PFGaugePreview(panel, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
previewSizer.Add(self.wndPreview103105, 1, wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
footerSizer.Add(previewSizer, 1, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
buttonsSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.cbLink = wx.CheckBox(panel, wx.ID_ANY, "Link Colors", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
buttonsSizer.Add(self.cbLink, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5)
|
||||
|
||||
self.sliderGradientStart = wx.Slider(panel, wx.ID_ANY, self.gradientStart, -100, 100, wx.DefaultPosition,
|
||||
(127, -1), wx.SL_HORIZONTAL | wx.SL_LABELS)
|
||||
buttonsSizer.Add(self.sliderGradientStart, 1, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.btnRestore = wx.Button(panel, wx.ID_ANY, "Restore Defaults", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
buttonsSizer.Add(self.btnRestore, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.btnDump = wx.Button(panel, wx.ID_ANY, "Dump Colors", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
buttonsSizer.Add(self.btnDump, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
self.btnOk = wx.Button(panel, wx.ID_ANY, "Apply", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
buttonsSizer.Add(self.btnOk, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
|
||||
|
||||
footerSizer.Add(buttonsSizer, 1, wx.ALIGN_RIGHT, 5)
|
||||
mainSizer.Add(footerSizer, 0, wx.EXPAND, 5)
|
||||
|
||||
panel.SetSizer(mainSizer)
|
||||
|
||||
self.SetColours()
|
||||
|
||||
# self.Fit()
|
||||
# self.Layout()
|
||||
|
||||
self.sliderGradientStart.Bind(wx.EVT_SCROLL, self.OnGradientStartScroll)
|
||||
self.btnRestore.Bind(wx.EVT_BUTTON, self.RestoreDefaults)
|
||||
self.btnDump.Bind(wx.EVT_BUTTON, self.DumpColours)
|
||||
self.btnOk.Bind(wx.EVT_BUTTON, self.OnOk)
|
||||
|
||||
self.cp0100S.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
self.cp0100E.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
|
||||
self.cp100101S.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
self.cp100101E.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
|
||||
self.cp101103S.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
self.cp101103E.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
|
||||
self.cp103105S.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
self.cp103105E.Bind(wx.EVT_COLOURPICKER_CHANGED, self.OnColourChanged)
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("pref-gauges_big", "gui")
|
||||
|
||||
def InitDefaultColours(self):
|
||||
self.c0100S = wx.Colour(191, 191, 191, 255)
|
||||
self.c0100E = wx.Colour(96, 191, 0, 255)
|
||||
|
||||
self.c100101S = wx.Colour(191, 167, 96, 255)
|
||||
self.c100101E = wx.Colour(255, 191, 0, 255)
|
||||
|
||||
self.c101103S = wx.Colour(255, 191, 0, 255)
|
||||
self.c101103E = wx.Colour(255, 128, 0, 255)
|
||||
|
||||
self.c103105S = wx.Colour(255, 128, 0, 255)
|
||||
self.c103105E = wx.Colour(255, 0, 0, 255)
|
||||
self.gradientStart = -35
|
||||
|
||||
def SetColours(self):
|
||||
self.cp0100S.SetColour(self.c0100S)
|
||||
self.cp0100E.SetColour(self.c0100E)
|
||||
self.gauge0100S.SetColour(self.c0100S, self.c0100E)
|
||||
self.gauge0100M.SetColour(self.c0100S, self.c0100E)
|
||||
self.gauge0100E.SetColour(self.c0100S, self.c0100E)
|
||||
|
||||
self.gauge0100S.SetPercentages(0, 99.99)
|
||||
self.gauge0100M.SetPercentages(0, 99.99)
|
||||
self.gauge0100E.SetPercentages(0, 99.99)
|
||||
|
||||
self.gauge0100S.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge0100M.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge0100E.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.cp100101S.SetColour(self.c100101S)
|
||||
self.cp100101E.SetColour(self.c100101E)
|
||||
self.gauge100101S.SetColour(self.c100101S, self.c100101E)
|
||||
self.gauge100101M.SetColour(self.c100101S, self.c100101E)
|
||||
self.gauge100101E.SetColour(self.c100101S, self.c100101E)
|
||||
|
||||
self.gauge100101S.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge100101M.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge100101E.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.gauge100101S.SetPercentages(100, 100.99)
|
||||
self.gauge100101M.SetPercentages(100, 100.99)
|
||||
self.gauge100101E.SetPercentages(100, 100.99)
|
||||
|
||||
self.gauge100101S.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge100101M.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge100101E.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.cp101103S.SetColour(self.c101103S)
|
||||
self.cp101103E.SetColour(self.c101103E)
|
||||
self.gauge101103S.SetColour(self.c101103S, self.c101103E)
|
||||
self.gauge101103M.SetColour(self.c101103S, self.c101103E)
|
||||
self.gauge101103E.SetColour(self.c101103S, self.c101103E)
|
||||
|
||||
self.gauge101103S.SetPercentages(101, 102.99)
|
||||
self.gauge101103M.SetPercentages(101, 102.99)
|
||||
self.gauge101103E.SetPercentages(101, 102.99)
|
||||
|
||||
self.gauge101103S.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge101103M.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge101103E.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.cp103105S.SetColour(self.c103105S)
|
||||
self.cp103105E.SetColour(self.c103105E)
|
||||
self.gauge103105S.SetColour(self.c103105S, self.c103105E)
|
||||
self.gauge103105M.SetColour(self.c103105S, self.c103105E)
|
||||
self.gauge103105E.SetColour(self.c103105S, self.c103105E)
|
||||
|
||||
self.gauge103105S.SetPercentages(103, 104.99)
|
||||
self.gauge103105M.SetPercentages(103, 104.99)
|
||||
self.gauge103105E.SetPercentages(103, 104.99)
|
||||
|
||||
self.gauge103105S.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge103105M.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
self.gauge103105E.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.wndPreview0100.SetColour(self.c0100S, self.c0100E)
|
||||
self.wndPreview0100.SetPercentages(0, 99.99)
|
||||
self.wndPreview0100.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.wndPreview100101.SetColour(self.c100101S, self.c100101E)
|
||||
self.wndPreview100101.SetPercentages(100, 100.99)
|
||||
self.wndPreview100101.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.wndPreview101103.SetColour(self.c101103S, self.c101103E)
|
||||
self.wndPreview101103.SetPercentages(101, 102.99)
|
||||
self.wndPreview101103.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
self.wndPreview103105.SetColour(self.c103105S, self.c103105E)
|
||||
self.wndPreview103105.SetPercentages(103, 104.99)
|
||||
self.wndPreview103105.SetGradientStart(self.sliderGradientStart.GetValue())
|
||||
|
||||
def OnGradientStartScroll(self, event):
|
||||
self.gradientStart = self.sliderGradientStart.GetValue()
|
||||
self.SetColours()
|
||||
event.Skip()
|
||||
|
||||
@staticmethod
|
||||
def OnOk(event):
|
||||
# Apply New Settings
|
||||
event.Skip()
|
||||
|
||||
def DumpColours(self, event):
|
||||
print(("Gradient start: %d" % self.sliderGradientStart.GetValue()))
|
||||
print((" 0 <-> 100 Start: ", self.c0100S, " End: ", self.c0100E))
|
||||
print(("100 <-> 101 Start: ", self.c100101S, " End: ", self.c100101E))
|
||||
print(("101 <-> 103 Start: ", self.c101103S, " End: ", self.c101103E))
|
||||
print(("103 <-> 105 Start: ", self.c103105S, " End: ", self.c103105E))
|
||||
|
||||
event.Skip()
|
||||
|
||||
def RestoreDefaults(self, event):
|
||||
self.InitDefaultColours()
|
||||
self.sliderGradientStart.SetValue(self.gradientStart)
|
||||
self.SetColours()
|
||||
|
||||
event.Skip()
|
||||
|
||||
def OnColourChanged(self, event):
|
||||
|
||||
color = event.EventObject.GetColour()
|
||||
cpObj = event.EventObject
|
||||
|
||||
if cpObj == self.cp0100S:
|
||||
self.c0100S = color
|
||||
|
||||
if cpObj == self.cp0100E:
|
||||
self.c0100E = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c100101S = color
|
||||
|
||||
if cpObj == self.cp100101S:
|
||||
self.c100101S = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c0100E = color
|
||||
|
||||
if cpObj == self.cp100101E:
|
||||
self.c100101E = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c101103S = color
|
||||
|
||||
if cpObj == self.cp101103S:
|
||||
self.c101103S = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c100101E = color
|
||||
|
||||
if cpObj == self.cp101103E:
|
||||
self.c101103E = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c103105S = color
|
||||
|
||||
if cpObj == self.cp103105S:
|
||||
self.c103105S = color
|
||||
if self.cbLink.IsChecked():
|
||||
self.c101103E = color
|
||||
|
||||
if cpObj == self.cp103105E:
|
||||
self.c103105E = color
|
||||
|
||||
self.SetColours()
|
||||
event.Skip()
|
||||
|
||||
def __del__(self):
|
||||
pass
|
||||
|
||||
|
||||
PFGaugePref.register()
|
||||
@@ -24,9 +24,9 @@ class PFGeneralPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
helpCursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW)
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
@@ -86,6 +86,10 @@ class PFGeneralPref(PreferenceView):
|
||||
'When disabled, reloads charges just in selected modules. Action can be reversed by holding Ctrl or Alt key while changing charge.'))
|
||||
mainSizer.Add(self.cbReloadAll, 0, wx.ALL | wx.EXPAND, 5)
|
||||
|
||||
self.rbAddLabels = wx.RadioBox(panel, -1, "Extra info in Additions panel tab names", wx.DefaultPosition, wx.DefaultSize, ["None", "Quantity of active items", "Quantity of all items"], 1, wx.RA_SPECIFY_COLS)
|
||||
mainSizer.Add(self.rbAddLabels, 0, wx.EXPAND | wx.TOP | wx.RIGHT | wx.BOTTOM, 10)
|
||||
self.rbAddLabels.Bind(wx.EVT_RADIOBOX, self.OnAddLabelsChange)
|
||||
|
||||
self.sFit = Fit.getInstance()
|
||||
|
||||
self.cbGlobalChar.SetValue(self.sFit.serviceFittingOptions["useGlobalCharacter"])
|
||||
@@ -101,6 +105,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.cbOpenFitInNew.SetValue(self.sFit.serviceFittingOptions["openFitInNew"])
|
||||
self.cbShowShipBrowserTooltip.SetValue(self.sFit.serviceFittingOptions["showShipBrowserTooltip"])
|
||||
self.cbReloadAll.SetValue(self.sFit.serviceFittingOptions["ammoChangeAll"])
|
||||
self.rbAddLabels.SetSelection(self.sFit.serviceFittingOptions["additionsLabels"])
|
||||
|
||||
self.cbGlobalChar.Bind(wx.EVT_CHECKBOX, self.OnCBGlobalCharStateChange)
|
||||
self.cbDefaultCharImplants.Bind(wx.EVT_CHECKBOX, self.OnCBDefaultCharImplantsStateChange)
|
||||
@@ -187,6 +192,13 @@ class PFGeneralPref(PreferenceView):
|
||||
def onCBReloadAll(self, event):
|
||||
self.sFit.serviceFittingOptions["ammoChangeAll"] = self.cbReloadAll.GetValue()
|
||||
|
||||
def OnAddLabelsChange(self, event):
|
||||
self.sFit.serviceFittingOptions["additionsLabels"] = event.GetInt()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.sFit.refreshFit(fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
|
||||
event.Skip()
|
||||
|
||||
def getImage(self):
|
||||
return BitmapLoader.getBitmap("prefs_settings", "gui")
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class PFHTMLExportPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -23,7 +23,7 @@ class PFGeneralPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, "(Cannot be changed while pyfa is running. Set via command line switches.)",
|
||||
wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
|
||||
@@ -28,8 +28,7 @@ class PFMarketPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, "Market && Prices", wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -24,8 +24,7 @@ class PFNetworkPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -23,8 +23,7 @@ class PFStatViewPref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
|
||||
"Changes require restart of pyfa to take effect.",
|
||||
|
||||
@@ -24,7 +24,7 @@ class PFUpdatePref(PreferenceView):
|
||||
self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0)
|
||||
self.stTitle.Wrap(-1)
|
||||
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
|
||||
mainSizer.Add(self.stTitle, 0, wx.ALL, 5)
|
||||
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
|
||||
mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
|
||||
|
||||
@@ -283,9 +283,7 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
event.Skip()
|
||||
|
||||
def editCheckEsc(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.RestoreEditButton()
|
||||
else:
|
||||
event.Skip()
|
||||
@@ -344,13 +342,12 @@ class FitItem(SFItem.SFBrowserItem):
|
||||
if mstate.GetModifiers() == wx.MOD_SHIFT or mstate.MiddleIsDown():
|
||||
self.deleteFit()
|
||||
else:
|
||||
dlg = wx.MessageDialog(
|
||||
self, "Do you really want to delete this fit?", "Confirm Delete",
|
||||
wx.YES | wx.NO | wx.ICON_QUESTION)
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
self.deleteFit()
|
||||
else:
|
||||
dlg.Destroy()
|
||||
with wx.MessageDialog(
|
||||
self.GetTopLevelParent(), "Do you really want to delete this fit?", "Confirm Delete",
|
||||
wx.YES | wx.NO | wx.ICON_QUESTION
|
||||
) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
self.deleteFit()
|
||||
|
||||
def deleteFit(self, event=None):
|
||||
pyfalog.debug("Deleting ship fit.")
|
||||
|
||||
@@ -109,10 +109,10 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
|
||||
def OnBrowserSearchBoxKeyPress(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.BrowserSearchBox.Show(False)
|
||||
elif event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
elif keycode == wx.WXK_BACK and modifiers == wx.MOD_CONTROL:
|
||||
HandleCtrlBackspace(self.BrowserSearchBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
@@ -176,9 +176,7 @@ class ShipItem(SFItem.SFBrowserItem):
|
||||
self.Refresh()
|
||||
|
||||
def editCheckEsc(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.tcFitName.Show(False)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
@@ -36,7 +36,6 @@ class RechargeViewFull(StatsView):
|
||||
self.parent = parent
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.toggleEffective)
|
||||
self.effective = True
|
||||
|
||||
def getHeaderText(self, fit):
|
||||
return "Recharge rates"
|
||||
@@ -45,9 +44,15 @@ class RechargeViewFull(StatsView):
|
||||
width, height = self.parent.GetTextExtent(text)
|
||||
return width
|
||||
|
||||
@property
|
||||
def effective(self):
|
||||
try:
|
||||
return self.parent.nameViewMap['resistancesViewFull'].showEffective
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def toggleEffective(self, event):
|
||||
event.Skip()
|
||||
self.effective = event.effective
|
||||
sFit = Fit.getInstance()
|
||||
self.refreshPanel(sFit.getFit(self.mainFrame.getActiveFit()))
|
||||
|
||||
@@ -104,8 +109,7 @@ class RechargeViewFull(StatsView):
|
||||
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything interesting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
unit = " EHP/s" if self.parent.nameViewMap['resistancesViewFull'].showEffective else " HP/s"
|
||||
unit = " EHP/s" if self.effective else " HP/s"
|
||||
|
||||
for stability in ("reinforced", "sustained"):
|
||||
if stability == "reinforced" and fit is not None:
|
||||
|
||||
@@ -100,6 +100,10 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
def refreshPanel(self, fit):
|
||||
# If we did anything interesting, we'd update our labels to reflect the new fit's stats here
|
||||
|
||||
sensorValues = {
|
||||
"main": lambda: fit.scanStrength,
|
||||
"jamChance": lambda: fit.jamChance}
|
||||
|
||||
cargoNamesOrder = OrderedDict((
|
||||
("fleetHangarCapacity", "Fleet hangar"),
|
||||
("shipMaintenanceBayCapacity", "Maintenance bay"),
|
||||
@@ -117,8 +121,7 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
("specialSalvageHoldCapacity", "Salvage hold"),
|
||||
("specialCommandCenterHoldCapacity", "Command center hold"),
|
||||
("specialPlanetaryCommoditiesHoldCapacity", "Planetary goods hold"),
|
||||
("specialQuafeHoldCapacity", "Quafe hold")
|
||||
))
|
||||
("specialQuafeHoldCapacity", "Quafe hold")))
|
||||
|
||||
cargoValues = {
|
||||
"main": lambda: fit.ship.getModifiedItemAttr("capacity"),
|
||||
@@ -138,13 +141,12 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
"specialSalvageHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSalvageHoldCapacity"),
|
||||
"specialCommandCenterHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialCommandCenterHoldCapacity"),
|
||||
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),
|
||||
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity")
|
||||
}
|
||||
"specialQuafeHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialQuafeHoldCapacity")}
|
||||
|
||||
stats = (("labelTargets", {"main": lambda: fit.maxTargets}, 3, 0, 0, ""),
|
||||
("labelRange", {"main": lambda: fit.maxTargetRange / 1000}, 3, 0, 0, "km"),
|
||||
("labelScanRes", {"main": lambda: fit.ship.getModifiedItemAttr("scanResolution")}, 3, 0, 0, "mm"),
|
||||
("labelSensorStr", {"main": lambda: fit.scanStrength}, 3, 0, 0, ""),
|
||||
("labelSensorStr", sensorValues, 3, 0, 0, ""),
|
||||
("labelCtrlRange", {"main": lambda: fit.extraAttributes["droneControlRange"] / 1000}, 3, 0, 0, "km"),
|
||||
("labelFullSpeed", {"main": lambda: fit.maxSpeed}, 3, 0, 0, "m/s"),
|
||||
("labelFullAlignTime", {"main": lambda: fit.alignTime}, 3, 0, 0, "s"),
|
||||
@@ -176,6 +178,15 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
unit))
|
||||
else:
|
||||
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
|
||||
elif labelName == "labelSensorStr":
|
||||
ecmChance = otherValues["jamChance"]
|
||||
ecmChance = round(ecmChance, 1)
|
||||
if ecmChance:
|
||||
label.SetLabel("{} ({}%)".format(
|
||||
formatAmount(mainValue, prec, lowest, highest),
|
||||
formatAmount(ecmChance, 3, 0, 0)))
|
||||
else:
|
||||
label.SetLabel("{}".format(formatAmount(mainValue, prec, lowest, highest)))
|
||||
else:
|
||||
label.SetLabel("%s %s" % (formatAmount(mainValue, prec, lowest, highest), unit))
|
||||
# Tooltip stuff
|
||||
@@ -195,10 +206,14 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
warpScrambleStatus = "Warp Core Strength: %.1f" % 0
|
||||
label.SetToolTip(wx.ToolTip("%s\n%s" % (maxWarpDistance, warpScrambleStatus)))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
ecmChance = otherValues["jamChance"]
|
||||
ecmChance = round(ecmChance, 1)
|
||||
if ecmChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: {}\n{}% chance to be jammed".format(
|
||||
fit.scanType,
|
||||
formatAmount(ecmChance, 3, 0, 0))))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
|
||||
label.SetToolTip(wx.ToolTip("Type: {}".format(fit.scanType)))
|
||||
elif labelName == "labelFullAlignTime":
|
||||
alignTime = "Align:\t%.3fs" % mainValue
|
||||
mass = 'Mass:\t{:,.0f}kg'.format(fit.ship.getModifiedItemAttr("mass"))
|
||||
@@ -225,14 +240,6 @@ class TargetingMiscViewMinimal(StatsView):
|
||||
label.SetToolTip(wx.ToolTip("%s\n%s" % (maxWarpDistance, warpScrambleStatus)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelSensorStr":
|
||||
if fit:
|
||||
if fit.jamChance > 0:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s\n%.1f%% Chance of Jam" % (fit.scanType, fit.jamChance)))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip("Type: %s" % fit.scanType))
|
||||
else:
|
||||
label.SetToolTip(wx.ToolTip(""))
|
||||
elif labelName == "labelFullCargo":
|
||||
if fit:
|
||||
cachedCargo = self._cachedValues[counter]
|
||||
|
||||
60
gui/builtinViewColumns/dampScanRes.py
Normal file
60
gui/builtinViewColumns/dampScanRes.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
from eos.saveddata.fit import Fit
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from eos.utils.float import floatUnerr
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
class DampScanResColumn(ViewColumn):
|
||||
|
||||
name = 'Damp ScanRes'
|
||||
|
||||
def __init__(self, fittingView, params):
|
||||
ViewColumn.__init__(self, fittingView)
|
||||
self.imageId = fittingView.imageList.GetImageIndex(74, 'icons')
|
||||
self.bitmap = BitmapLoader.getBitmap(74, 'icons')
|
||||
self.mask = wx.LIST_MASK_IMAGE
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
mult = 1
|
||||
if isinstance(stuff, Fit):
|
||||
mult = floatUnerr(stuff.getDampMultScanRes())
|
||||
if mult == 1:
|
||||
text = ''
|
||||
else:
|
||||
text = '{}%'.format(formatAmount((mult - 1) * 100, 3, 0, 0, forceSign=True))
|
||||
return text
|
||||
|
||||
def getImageId(self, stuff):
|
||||
return -1
|
||||
|
||||
def getToolTip(self, stuff):
|
||||
return 'Scan resolution dampening'
|
||||
|
||||
|
||||
DampScanResColumn.register()
|
||||
@@ -612,7 +612,10 @@ class Miscellanea(ViewColumn):
|
||||
fit = Fit.getInstance().getFit(self.fittingView.getActiveFit())
|
||||
ehpTotal = fit.ehp
|
||||
hpTotal = fit.hp
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
try:
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
except KeyError:
|
||||
useEhp = False
|
||||
tooltip = "{0} restored over duration using charges (plus reload)".format(boosted_attribute)
|
||||
|
||||
if useEhp and boosted_attribute == "HP" and "Remote" not in itemGroup:
|
||||
@@ -686,9 +689,8 @@ class Miscellanea(ViewColumn):
|
||||
baseRange = stuff.getModifiedChargeAttr("baseScanRange")
|
||||
if not scanStr or not baseRange:
|
||||
return "", None
|
||||
strTwoAu = scanStr / (2.0 / baseRange)
|
||||
text = "{0}".format(formatAmount(strTwoAu, 3, 0, 3))
|
||||
tooltip = "Scan strength with 2 AU scan range"
|
||||
text = "{}".format(formatAmount(scanStr, 4, 0, 3))
|
||||
tooltip = "Scan strength at {} AU scan range".format(formatAmount(baseRange, 3, 0, 0))
|
||||
return text, tooltip
|
||||
else:
|
||||
return "", None
|
||||
|
||||
61
gui/builtinViewColumns/projectionRange.py
Normal file
61
gui/builtinViewColumns/projectionRange.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# coding: utf-8
|
||||
# =============================================================================
|
||||
# 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 logbook import Logger
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.fit import Fit
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class ProjectionRangeColumn(ViewColumn):
|
||||
|
||||
name = 'Projection Range'
|
||||
|
||||
def __init__(self, fittingView, params):
|
||||
super().__init__(fittingView)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.imageId = fittingView.imageList.GetImageIndex(1391, "icons")
|
||||
self.bitmap = BitmapLoader.getBitmap(1391, "icons")
|
||||
self.mask = wx.LIST_MASK_IMAGE
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, Fit):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
info = stuff.getProjectionInfo(fitID)
|
||||
projRange = info.projectionRange
|
||||
else:
|
||||
projRange = getattr(stuff, 'projectionRange', None)
|
||||
if projRange is None:
|
||||
return ''
|
||||
return formatAmount(projRange, 3, 0, 3, unitName='m')
|
||||
|
||||
def getToolTip(self, mod):
|
||||
return 'Projection Range'
|
||||
|
||||
|
||||
ProjectionRangeColumn.register()
|
||||
@@ -280,12 +280,12 @@ class FittingView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
modifiers = event.GetModifiers()
|
||||
if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
elif keycode == 65 and modifiers == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
|
||||
modules = [m for m in self.getSelectedMods() if not m.isEmpty]
|
||||
self.removeModule(modules)
|
||||
event.Skip()
|
||||
@@ -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:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
# noinspection PyPackageRequirements
|
||||
@@ -10,6 +12,12 @@ from gui.marketBrowser import SearchBox
|
||||
from service.market import Market
|
||||
|
||||
|
||||
def stripHtml(text):
|
||||
text = re.sub('<\s*br\s*/?\s*>', '\n', text)
|
||||
text = re.sub('</?[^/]+?(/\s*)?>', '', text)
|
||||
return text
|
||||
|
||||
|
||||
class BaseImplantEditorView(wx.Panel):
|
||||
|
||||
def addMarketViewImage(self, iconFile):
|
||||
@@ -68,8 +76,10 @@ class BaseImplantEditorView(wx.Panel):
|
||||
|
||||
self.SetSizer(pmainSizer)
|
||||
|
||||
# Populate the market tree
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.hoveredRightListRow = None
|
||||
|
||||
# Populate the market tree
|
||||
sMkt = Market.getInstance()
|
||||
for mktGrp in sMkt.getImplantTree():
|
||||
iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(mktGrp))
|
||||
@@ -82,9 +92,13 @@ class BaseImplantEditorView(wx.Panel):
|
||||
# Bind the event to replace dummies by real data
|
||||
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
|
||||
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.itemSelected)
|
||||
self.availableImplantsTree.Bind(wx.EVT_MOTION, self.OnLeftTreeMouseMove)
|
||||
self.availableImplantsTree.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeftTreeMouseLeave)
|
||||
|
||||
self.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemSelected)
|
||||
|
||||
self.pluggedImplantsTree.Bind(wx.EVT_MOTION, self.OnRightListMouseMove)
|
||||
|
||||
# Bind add & remove buttons
|
||||
self.btnAdd.Bind(wx.EVT_BUTTON, self.itemSelected)
|
||||
self.btnRemove.Bind(wx.EVT_BUTTON, self.removeItem)
|
||||
@@ -193,6 +207,55 @@ class BaseImplantEditorView(wx.Panel):
|
||||
self.removeImplantFromContext(self.implants[pos])
|
||||
self.update()
|
||||
|
||||
# Due to https://github.com/wxWidgets/Phoenix/issues/1372 we cannot set tooltips on
|
||||
# tree itself; work this around with following two methods, by setting tooltip to
|
||||
# parent window
|
||||
def OnLeftTreeMouseMove(self, event):
|
||||
event.Skip()
|
||||
treeItemId, _ = self.availableImplantsTree.HitTest(event.Position)
|
||||
if not treeItemId:
|
||||
if self.hoveredLeftTreeTypeID is not None:
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.SetToolTip(None)
|
||||
return
|
||||
item = self.availableImplantsTree.GetItemData(treeItemId)
|
||||
isImplant = getattr(item, 'isImplant', False)
|
||||
if not isImplant:
|
||||
if self.hoveredLeftTreeTypeID is not None:
|
||||
self.hoveredLeftTreeTypeID = None
|
||||
self.SetToolTip(None)
|
||||
return
|
||||
if self.hoveredLeftTreeTypeID == item.ID:
|
||||
return
|
||||
if self.ToolTip is not None:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredLeftTreeTypeID = item.ID
|
||||
toolTip = wx.ToolTip(stripHtml(item.description))
|
||||
toolTip.SetMaxWidth(self.GetSize().Width)
|
||||
self.SetToolTip(toolTip)
|
||||
|
||||
def OnLeftTreeMouseLeave(self, event):
|
||||
event.Skip()
|
||||
self.SetToolTip(None)
|
||||
|
||||
def OnRightListMouseMove(self, event):
|
||||
event.Skip()
|
||||
row, _, col = self.pluggedImplantsTree.HitTestSubItem(event.Position)
|
||||
if row != self.hoveredRightListRow:
|
||||
if self.pluggedImplantsTree.ToolTip is not None:
|
||||
self.pluggedImplantsTree.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredRightListRow = row
|
||||
try:
|
||||
implant = self.implants[row]
|
||||
except IndexError:
|
||||
self.pluggedImplantsTree.SetToolTip(None)
|
||||
else:
|
||||
toolTip = wx.ToolTip(stripHtml(implant.item.description))
|
||||
toolTip.SetMaxWidth(self.pluggedImplantsTree.GetSize().Width)
|
||||
self.pluggedImplantsTree.SetToolTip(toolTip)
|
||||
|
||||
|
||||
class AvailableImplantsView(d.Display):
|
||||
DEFAULT_COLS = ["attr:implantness",
|
||||
@@ -212,6 +275,7 @@ class ItemView(d.Display):
|
||||
self.parent = parent
|
||||
self.searchBox = parent.searchBox
|
||||
|
||||
self.hoveredRow = None
|
||||
self.items = []
|
||||
|
||||
# Bind search actions
|
||||
@@ -220,6 +284,8 @@ class ItemView(d.Display):
|
||||
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
|
||||
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
|
||||
|
||||
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
||||
|
||||
def clearSearch(self, event=None):
|
||||
if self.IsShown():
|
||||
self.parent.availableImplantsTree.Show()
|
||||
@@ -255,3 +321,20 @@ class ItemView(d.Display):
|
||||
self.items = sorted(list(items), key=lambda i: i.name)
|
||||
|
||||
self.update(self.items)
|
||||
|
||||
def OnMouseMove(self, event):
|
||||
event.Skip()
|
||||
row, _, col = self.HitTestSubItem(event.Position)
|
||||
if row != self.hoveredRow:
|
||||
if self.ToolTip is not None:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
self.hoveredRow = row
|
||||
try:
|
||||
item = self.items[row]
|
||||
except IndexError:
|
||||
self.SetToolTip(None)
|
||||
else:
|
||||
toolTip = wx.ToolTip(stripHtml(item.description))
|
||||
toolTip.SetMaxWidth(self.GetSize().Width)
|
||||
self.SetToolTip(toolTip)
|
||||
|
||||
@@ -155,8 +155,8 @@ class CharacterEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="Character Editor", resizeable=True,
|
||||
pos=wx.DefaultPosition, size=wx.Size(640, 600))
|
||||
parent, id=wx.ID_ANY, title="Character Editor", resizeable=True, pos=wx.DefaultPosition,
|
||||
size=wx.Size(950, 650) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 600))
|
||||
|
||||
i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui"))
|
||||
self.SetIcon(i)
|
||||
@@ -209,6 +209,7 @@ class CharacterEditor(AuxiliaryFrame):
|
||||
self.SetSizer(mainSizer)
|
||||
self.Layout()
|
||||
|
||||
self.SetMinSize(self.GetSize())
|
||||
self.Centre(wx.BOTH)
|
||||
|
||||
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
||||
@@ -257,9 +258,7 @@ class CharacterEditor(AuxiliaryFrame):
|
||||
wx.PostEvent(self, GE.CharListUpdated())
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
#
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from functools import lru_cache
|
||||
|
||||
import wx
|
||||
import wx.lib.newevent
|
||||
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils import draw
|
||||
from gui.utils import color as color_utils
|
||||
from gui.utils import color as color_utils, draw, fonts
|
||||
from service.fit import Fit
|
||||
from gui.utils import fonts
|
||||
|
||||
|
||||
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
|
||||
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
|
||||
@@ -89,11 +92,15 @@ class PageAdding(_PageAdding, VetoAble):
|
||||
|
||||
class ChromeNotebook(wx.Panel):
|
||||
|
||||
def __init__(self, parent, can_add=True):
|
||||
def __init__(self, parent, can_add=True, tabWidthMode=0):
|
||||
"""
|
||||
Instance of Notebook. Initializes general layout, includes methods
|
||||
for setting current page, replacing pages, any public function for the
|
||||
notebook
|
||||
|
||||
width modes:
|
||||
- 0: legacy (all tabs have equal width)
|
||||
- 1: all tabs take just enough space to fit text
|
||||
"""
|
||||
super().__init__(parent, wx.ID_ANY, size=(-1, -1))
|
||||
|
||||
@@ -103,7 +110,7 @@ class ChromeNotebook(wx.Panel):
|
||||
main_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
tabs_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.tabs_container = _TabsContainer(self, can_add=can_add)
|
||||
self.tabs_container = _TabsContainer(self, can_add=can_add, tabWidthMode=tabWidthMode)
|
||||
tabs_sizer.Add(self.tabs_container, 0, wx.EXPAND)
|
||||
|
||||
if 'wxMSW' in wx.PlatformInfo:
|
||||
@@ -296,7 +303,14 @@ class ChromeNotebook(wx.Panel):
|
||||
|
||||
def SetPageTitle(self, i, text, refresh=True):
|
||||
tab = self.tabs_container.tabs[i]
|
||||
tab.text = text
|
||||
tab.baseText = text
|
||||
if refresh:
|
||||
self.tabs_container.AdjustTabsSize()
|
||||
self.Refresh()
|
||||
|
||||
def SetPageTitleExtra(self, i, text, refresh=True):
|
||||
tab = self.tabs_container.tabs[i]
|
||||
tab.extraText = text
|
||||
if refresh:
|
||||
self.tabs_container.AdjustTabsSize()
|
||||
self.Refresh()
|
||||
@@ -354,7 +368,8 @@ class _TabRenderer:
|
||||
height = max(height, self.min_height)
|
||||
|
||||
self.disabled = False
|
||||
self.text = text
|
||||
self.baseText = text
|
||||
self.extraText = ''
|
||||
self.tab_size = (width, height)
|
||||
self.closeable = closeable
|
||||
self.selected = False
|
||||
@@ -368,6 +383,10 @@ class _TabRenderer:
|
||||
self.position = (0, 0) # Not used internally for rendering - helper for tab container
|
||||
self.InitTab()
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.baseText + self.extraText
|
||||
|
||||
def SetPosition(self, position):
|
||||
self.position = position
|
||||
|
||||
@@ -685,15 +704,15 @@ class _AddRenderer:
|
||||
|
||||
class _TabsContainer(wx.Panel):
|
||||
def __init__(self, parent, pos=(50, 0), size=(100, 22), id=wx.ID_ANY,
|
||||
can_add=True):
|
||||
can_add=True, tabWidthMode=0):
|
||||
"""
|
||||
Defines the tab container. Handles functions such as tab selection and
|
||||
dragging, and defines minimum width of tabs (all tabs are of equal
|
||||
width, which is determined via widest tab). Also handles the tab
|
||||
preview, if any.
|
||||
"""
|
||||
|
||||
super().__init__(parent, id, pos, size)
|
||||
self.tabWidthMode = tabWidthMode
|
||||
|
||||
self.tabs = []
|
||||
self.width, self.height = size
|
||||
@@ -720,8 +739,7 @@ class _TabsContainer(wx.Panel):
|
||||
self.show_add_button = can_add
|
||||
|
||||
self.tab_container_width = self.width - self.reserved
|
||||
self.tab_min_width = self.width
|
||||
self.tab_shadow = _TabRenderer((self.tab_min_width, self.height + 1))
|
||||
self.fxBmps = {}
|
||||
|
||||
self.add_button = _AddRenderer()
|
||||
self.add_bitmap = self.add_button.Render()
|
||||
@@ -1173,7 +1191,7 @@ class _TabsContainer(wx.Panel):
|
||||
|
||||
if not tab.IsSelected():
|
||||
# drop shadow first
|
||||
mdc.DrawBitmap(self.fx_bmp, posx, posy, True)
|
||||
mdc.DrawBitmap(self.fxBmps[tab], posx, posy, True)
|
||||
bmp = tab.Render()
|
||||
img = bmp.ConvertToImage()
|
||||
img = img.AdjustChannels(1, 1, 1, 0.85)
|
||||
@@ -1191,7 +1209,7 @@ class _TabsContainer(wx.Panel):
|
||||
if selected:
|
||||
posx, posy = selected.GetPosition()
|
||||
# drop shadow first
|
||||
mdc.DrawBitmap(self.fx_bmp, posx, posy, True)
|
||||
mdc.DrawBitmap(self.fxBmps[selected], posx, posy, True)
|
||||
|
||||
bmp = selected.Render()
|
||||
|
||||
@@ -1211,16 +1229,21 @@ class _TabsContainer(wx.Panel):
|
||||
|
||||
def UpdateTabFX(self):
|
||||
""" Updates tab drop shadow bitmap """
|
||||
self.tab_shadow.SetSize((self.tab_min_width, self.height + 1))
|
||||
fx_bmp = self.tab_shadow.Render()
|
||||
self.fxBmps.clear()
|
||||
for tab in self.tabs:
|
||||
tabW, tabH = tab.tab_size
|
||||
self.fxBmps[tab] = self.GetTabFx(tabW, self.height + 1)
|
||||
|
||||
@lru_cache(maxsize=50)
|
||||
def GetTabFx(self, width, height):
|
||||
renderer = _TabRenderer((width, height))
|
||||
fx_bmp = renderer.Render()
|
||||
img = fx_bmp.ConvertToImage()
|
||||
if not img.HasAlpha():
|
||||
img.InitAlpha()
|
||||
img = img.Blur(2)
|
||||
img = img.AdjustChannels(0.3, 0.3, 0.3, 0.35)
|
||||
|
||||
self.fx_bmp = wx.Bitmap(img)
|
||||
return wx.Bitmap(img)
|
||||
|
||||
def AddTab(self, title=wx.EmptyString, img=None, closeable=False):
|
||||
self.ClearTabsSelected()
|
||||
@@ -1262,29 +1285,74 @@ class _TabsContainer(wx.Panel):
|
||||
Adjust tab sizes to ensure that they are all consistent and can fit into
|
||||
the tab container.
|
||||
"""
|
||||
if self.tabWidthMode == 1:
|
||||
if self.GetTabsCount() > 0:
|
||||
availableW = self.tab_container_width
|
||||
overlapSavedW = max(0, len(self.tabs)) * self.inclination * 2
|
||||
tabsGrouped = {}
|
||||
for tab in self.tabs:
|
||||
tabW, _ = tab.GetMinSize()
|
||||
tabsGrouped.setdefault(math.ceil(tabW), []).append(tab)
|
||||
clippedTabs = []
|
||||
clipW = max(tabsGrouped, default=0)
|
||||
|
||||
# first we loop through our tabs and calculate the the largest tab. This
|
||||
# is the size that we will base our calculations off
|
||||
def getUnclippedW():
|
||||
unclippedW = 0
|
||||
for w, tabs in tabsGrouped.items():
|
||||
unclippedW += w * len(tabs)
|
||||
return unclippedW
|
||||
while tabsGrouped:
|
||||
# Check if we're within width limit
|
||||
neededW = 0
|
||||
for w, tabs in tabsGrouped.items():
|
||||
neededW += w * len(tabs)
|
||||
if clippedTabs:
|
||||
neededW += clipW * len(clippedTabs)
|
||||
if neededW <= availableW + overlapSavedW:
|
||||
break
|
||||
# If we're not, extract widest tab group and mark it for clipping
|
||||
currentTabs = tabsGrouped.pop(max(tabsGrouped))
|
||||
clippedTabs.extend(currentTabs)
|
||||
proposedClipWidth = math.floor((availableW + overlapSavedW - getUnclippedW()) / len(clippedTabs))
|
||||
if not tabsGrouped or proposedClipWidth >= max(tabsGrouped, default=0):
|
||||
clipW = max(0, proposedClipWidth)
|
||||
break
|
||||
else:
|
||||
clipW = max(tabsGrouped)
|
||||
# Assign width for unclipped tabs
|
||||
for w, tabs in tabsGrouped.items():
|
||||
for tab in tabs:
|
||||
tab.SetSize((w, self.height))
|
||||
if clippedTabs:
|
||||
# Some width remains to be used due to rounding to integer
|
||||
extraWTotal = availableW + overlapSavedW - getUnclippedW() - clipW * len(clippedTabs)
|
||||
extraWPerTab = math.ceil(extraWTotal / len(clippedTabs))
|
||||
# Assign width for clipped tabs
|
||||
for tab in clippedTabs:
|
||||
extraW = min(extraWTotal, extraWPerTab)
|
||||
extraWTotal -= extraW
|
||||
tab.SetSize((clipW + extraW, self.height))
|
||||
else:
|
||||
# first we loop through our tabs and calculate the the largest tab. This
|
||||
# is the size that we will base our calculations off
|
||||
max_width = 100 # Tab should be at least 100
|
||||
for tab in self.tabs:
|
||||
mw, _ = tab.GetMinSize() # Tab min size includes tab contents
|
||||
max_width = max(mw, max_width)
|
||||
|
||||
max_width = 100 # Tab should be at least 100
|
||||
for tab in self.tabs:
|
||||
mw, _ = tab.GetMinSize() # Tab min size includes tab contents
|
||||
max_width = max(mw, max_width)
|
||||
tabWidth = 0
|
||||
# Divide tab container by number of tabs and add inclination. This will
|
||||
# return the ideal max size for the containers size
|
||||
if self.GetTabsCount() > 0:
|
||||
dx = self.tab_container_width / self.GetTabsCount() + self.inclination * 2
|
||||
tabWidth = min(dx, max_width)
|
||||
|
||||
# Divide tab container by number of tabs and add inclination. This will
|
||||
# return the ideal max size for the containers size
|
||||
if self.GetTabsCount() > 0:
|
||||
dx = self.tab_container_width / self.GetTabsCount() + self.inclination * 2
|
||||
self.tab_min_width = min(dx, max_width)
|
||||
|
||||
# Apply new size to all tabs
|
||||
for tab in self.tabs:
|
||||
tab.SetSize((self.tab_min_width, self.height))
|
||||
|
||||
if self.GetTabsCount() > 0:
|
||||
# update drop shadow based on new sizes
|
||||
self.UpdateTabFX()
|
||||
# Apply new size to all tabs
|
||||
for tab in self.tabs:
|
||||
tab.SetSize((tabWidth, self.height))
|
||||
|
||||
# update drop shadow based on new sizes
|
||||
self.UpdateTabFX()
|
||||
self.UpdateTabsPosition()
|
||||
|
||||
def UpdateTabsPosition(self, skip_tab=None):
|
||||
|
||||
@@ -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)),
|
||||
))
|
||||
|
||||
@@ -117,7 +120,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 +189,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)
|
||||
|
||||
|
||||
@@ -301,10 +301,13 @@ class Display(wx.ListCtrl):
|
||||
|
||||
def ensureSelection(self, clickedPos):
|
||||
"""
|
||||
On mac, when right-click on any item happens, it doesn't get selected.
|
||||
This method ensures that selection actually happens.
|
||||
On windows with Ctrl is pressed, or on Mac, when right-click on any item happens,
|
||||
the item doesn't get selected. This method ensures that only clicked item is selected.
|
||||
"""
|
||||
if 'wxMac' in wx.PlatformInfo:
|
||||
if (
|
||||
'wxMac' in wx.PlatformInfo or
|
||||
('wxMSW' in wx.PlatformInfo and wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL)
|
||||
):
|
||||
if clickedPos != -1:
|
||||
selectedPoss = self.getSelectedRows()
|
||||
if clickedPos not in selectedPoss:
|
||||
|
||||
@@ -65,9 +65,7 @@ class ErrorHandler:
|
||||
class ErrorFrame(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent=None, error_title='Error!'):
|
||||
super().__init__(
|
||||
parent, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition,
|
||||
size=wx.Size(500, 600), style=wx.STAY_ON_TOP)
|
||||
super().__init__(parent, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600))
|
||||
|
||||
from eos.config import gamedata_version, gamedata_date
|
||||
|
||||
|
||||
@@ -92,9 +92,7 @@ class EveFittings(AuxiliaryFrame):
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
@@ -248,9 +246,7 @@ class ExportToEve(AuxiliaryFrame):
|
||||
self.charChoice.SetSelection(0)
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
@@ -358,9 +354,7 @@ class SsoCharacterMgmt(AuxiliaryFrame):
|
||||
event.Skip()
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
||||
self.Close()
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
@@ -56,6 +56,7 @@ from .gui.localModule.replace import GuiReplaceLocalModuleCommand
|
||||
from .gui.localModule.swap import GuiSwapLocalModulesCommand
|
||||
from .gui.localModuleCargo.cargoToLocalModule import GuiCargoToLocalModuleCommand
|
||||
from .gui.localModuleCargo.localModuleToCargo import GuiLocalModuleToCargoCommand
|
||||
from .gui.projectedChangeProjectionRange import GuiChangeProjectedItemsProjectionRangeCommand
|
||||
from .gui.projectedChangeStates import GuiChangeProjectedItemStatesCommand
|
||||
from .gui.projectedDrone.add import GuiAddProjectedDroneCommand
|
||||
from .gui.projectedDrone.changeAmount import GuiChangeProjectedDroneAmountCommand
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user