Compare commits

...

126 Commits

Author SHA1 Message Date
DarkPhoenix
6b3e94729c Update staticdata and bump version 2019-10-31 15:05:14 +03:00
DarkPhoenix
fb48f2b5d4 Do not attempt to add entries to name maps if entity fetch failed 2019-10-30 16:46:34 +03:00
DarkPhoenix
cfffc77777 Change format name 2019-10-30 16:13:15 +03:00
DarkPhoenix
f7089f358d Fix stats export parenthesis 2019-10-30 16:12:33 +03:00
DarkPhoenix
06c4f2ce46 Bump version 2019-10-30 16:09:08 +03:00
Anton Vorobyov
83eb0abd92 Merge pull request #2070 from pyfa-org/json_in_repo
Store text staticdata in repo instead of binary
2019-10-30 16:05:55 +03:00
DarkPhoenix
4199b33c47 Merge branch 'master' into json_in_repo 2019-10-30 15:47:47 +03:00
Anton Vorobyov
23cd4bff5a Merge pull request #2069 from Gochim/master
[Updated] Implemented copying the currently open fit stats to the clipboard.
2019-10-30 15:44:53 +03:00
DarkPhoenix
b65f95fe77 Make sure to avoid doing DB updates avoiding sqlalchemy, as we're re-using the same session for pyfa now 2019-10-30 15:39:21 +03:00
DarkPhoenix
32160c94e1 Add extra metadata field which we use during gamedata DB checks 2019-10-30 15:26:26 +03:00
DarkPhoenix
ac02fba98b Move useless category IDs closer to context 2019-10-30 15:18:08 +03:00
DarkPhoenix
cde0108cba Change logging a little so info about DB being rebuilt is always printed to stdout 2019-10-30 15:16:13 +03:00
DarkPhoenix
39dc7e4a46 Compose DB out of data stored externally 2019-10-30 14:56:51 +03:00
Gochim
9943f784a8 Fixed code auto-checks for pull request 2019-10-30 13:34:54 +02:00
Gochim
88ce45f29e Merge remote-tracking branch 'origin/master' 2019-10-30 11:46:25 +02:00
Gochim
7157e876ca Fixed issue with mainFrame after merging 2019-10-30 11:46:03 +02:00
Gochim
0cf88cf7ca Added stats that were more or less agreed on in [Issue #2065] 2019-10-30 11:39:21 +02:00
Gochim
10dfdc3627 Added UI for new type of copying data about fit to the clipboard 2019-10-30 11:39:21 +02:00
Gochim
76bdefcda6 Fixed wording in contributing.md 2019-10-30 11:39:21 +02:00
Gochim
1c2c8cc5f9 Added UI for new type of copying data about fit to the clipboard 2019-10-30 11:39:21 +02:00
Alexander Maryanovsky
58f853de5b Implemented copying the currently open fit stats to the clipboard. 2019-10-30 11:39:21 +02:00
Gochim
c052297bf7 Added stats that were more or less agreed on in [Issue #2065] 2019-10-30 09:17:38 +02:00
DarkPhoenix
9e78cd1076 Fix drag-n-dropping module from market into specific empty slot 2019-10-28 13:23:40 +03:00
DarkPhoenix
79f4deacea Show hidden graphs on ctrl-alt-g 2019-10-28 12:56:34 +03:00
Gochim
ff42c4c711 Added UI for new type of copying data about fit to the clipboard 2019-10-26 18:36:38 +03:00
DarkPhoenix
02d31d49d8 Implement graph types to pick best ECM burst + damps ship 2019-10-26 00:30:45 +03:00
DarkPhoenix
64f47fcc24 Do not choke on fits for unknown ships 2019-10-25 01:00:32 +03:00
DarkPhoenix
0ceb8acd64 Rename some fields for the hidden graph 2019-10-24 23:15:38 +03:00
DarkPhoenix
78579e2e13 Adjust ECM burst + damp graph 2019-10-24 23:13:20 +03:00
DarkPhoenix
cf4e1d3935 Consistency fixes 2019-10-24 14:09:08 +03:00
DarkPhoenix
d1be0bb680 Add scanres vs locktime on active fit graph as experimental feature 2019-10-24 13:52:34 +03:00
Gochim
384d9f4614 Fixed wording in contributing.md 2019-10-21 13:22:00 +03:00
Gochim
47434c68f9 Added UI for new type of copying data about fit to the clipboard 2019-10-20 15:25:06 +03:00
Gochim
af88afb6b5 Merge branch 'export-stats' of https://github.com/m-sasha/PyfaAT into m-sasha-export-stats
# Conflicts:
#	gui/mainFrame.py
#	gui/mainMenuBar.py
2019-10-20 14:48:00 +03:00
Anton Vorobyov
536eb1efa5 Merge pull request #2064 from Gochim/master
Merged some additional info from wiki. Improved on readability
2019-10-17 17:19:10 +03:00
Anton Vorobyov
c4c763089e Merge branch 'master' into master 2019-10-17 17:19:00 +03:00
DarkPhoenix
cdfd4c0d8e Invoke pip the same way as on Windows 2019-10-17 17:01:10 +03:00
DarkPhoenix
f9bb8836e5 Upgrade pip when building mac distribution 2019-10-17 16:59:38 +03:00
Gochim
58b2634c8c Merged info from wiki. Improved on readability 2019-10-17 16:50:40 +03:00
Anton Vorobyov
093ae008ce Merge pull request #2063 from Gochim/master
Re-written in much greater detail instructions how to set up the project
2019-10-17 16:05:31 +03:00
Gochim
5f62fc0cdc Merge remote-tracking branch 'origin/master' 2019-10-16 22:08:33 +03:00
Gochim
e7a4b4ac26 Added section "Setting up the project with PyCharm" 2019-10-16 22:07:10 +03:00
Gochim
66e9944cb5 Added "Setting up the project manually" instructions 2019-10-16 22:07:10 +03:00
DIvanchenko
ad8528c248 Plan for CONTRIBUTING.md. Updated link to CONTRIBUTION.md. 2019-10-16 22:06:19 +03:00
DIvanchenko
07d22cd8e4 Created link to CONTRIBUTION.md. Fixed heading 2019-10-16 22:06:19 +03:00
DarkPhoenix
a6d5922d77 Bump minor version, as database has been updated 2019-10-16 16:33:43 +03:00
Ryan Holmes
958d7bff99 Update .appveyor.yml 2019-10-16 09:27:22 -04:00
Ryan Holmes
7819b80be4 Update .appveyor.yml 2019-10-16 09:25:14 -04:00
Gochim
2ca50a4658 Added section "Setting up the project with PyCharm" 2019-10-15 23:08:21 +03:00
Gochim
09ff4fd128 Added "Setting up the project manually" instructions 2019-10-15 20:17:48 +03:00
DarkPhoenix
3e53863f9e Bump version 2019-10-15 17:09:15 +03:00
DarkPhoenix
63d2289f97 Update database to 1585794 2019-10-15 17:06:42 +03:00
DarkPhoenix
2663ef2e66 Make mutated module import consider old 1mn gravid mutaplasmid name 2019-10-14 18:26:22 +03:00
DIvanchenko
d4bdf47d62 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	README.md
2019-10-10 18:04:24 +03:00
DIvanchenko
660ee7c4bf Plan for CONTRIBUTING.md. Updated link to CONTRIBUTION.md. 2019-10-10 18:03:28 +03:00
DIvanchenko
1db1f3070b Created link to CONTRIBUTION.md. Fixed heading 2019-10-10 10:50:56 +03:00
DIvanchenko
3dba82c497 Created link to CONTRIBUTION.md. Fixed heading 2019-10-10 10:49:54 +03:00
DarkPhoenix
25e7b7a9f7 Fix RMB in graph fit list 2019-10-09 23:09:09 +03:00
DarkPhoenix
9582212ae0 Bump version 2019-10-09 19:38:25 +03:00
DarkPhoenix
7d2b60c327 Fix addition of drones via alt-dclick 2019-10-09 16:24:21 +03:00
DarkPhoenix
0121a0064e Show unknown meta groups as normal, also add standup faction group to faction tab 2019-10-09 14:38:08 +03:00
DarkPhoenix
2aa96fc819 Do not crash when user cancels setting of projection range 2019-10-09 13:30:01 +03:00
DarkPhoenix
8d81db0a3a Change what kind of strength is displayed for probe launchers 2019-10-09 13:13:40 +03:00
DarkPhoenix
e5ba35fde9 Pass effect in all effect handler calls 2019-10-08 23:41:26 +03:00
DarkPhoenix
885cd32cb0 Fix EFS export after adding projection range support 2019-10-08 23:11:58 +03:00
DarkPhoenix
18d8ed6558 Take modifier key info from event where possible 2019-10-08 20:50:14 +03:00
DarkPhoenix
9618ece4b4 Bump version 2019-10-08 19:50:57 +03:00
DarkPhoenix
a80a77a422 Update effects file 2019-10-08 19:50:38 +03:00
DarkPhoenix
3806be3ddd Add capsule renders 2019-10-08 19:46:15 +03:00
DarkPhoenix
3e803fef30 Actually expose capsule to a shuttle group 2019-10-08 19:37:32 +03:00
DarkPhoenix
12956d435a Update database to 1581357 and do necessary schema changes to support changed source data 2019-10-08 19:28:24 +03:00
DarkPhoenix
a3381007f3 Use numbers in default values instead of strings to avoid type errors 2019-10-07 16:58:11 +03:00
DarkPhoenix
1efe4ee5e5 Enable showing capsules in shuttle group 2019-10-05 10:27:08 +03:00
DarkPhoenix
ec21f93d3c Fix background color of panels behind buttons 2019-10-04 01:25:38 +03:00
DarkPhoenix
f384b32ed6 Remove unused file 2019-10-03 14:30:50 +03:00
DarkPhoenix
22d8f34c75 Fix pref panel titles on linux 2019-10-03 14:29:47 +03:00
DarkPhoenix
6128cd8322 Update once again
Cut everything outside of window
2019-10-03 13:34:42 +03:00
DarkPhoenix
386f403430 Update screenshot 2019-10-03 13:32:36 +03:00
DarkPhoenix
5f7d9aea89 Add amount of text in notes to notes tab title 2019-10-03 13:02:08 +03:00
DarkPhoenix
b367c449a9 Few more readme fixes 2019-10-03 09:56:53 +03:00
DarkPhoenix
26b3dff9d4 Update readme 2019-10-03 09:47:43 +03:00
DarkPhoenix
873a62e3f0 Make error dialog window like any other auxiliary window - do not keep it on top 2019-10-02 22:08:37 +03:00
Anton Vorobyov
d967ab375e Merge pull request #2050 from ZeroPointEnergy/feature/images_from_zip
Support loading images from zip
2019-10-02 19:16:50 +03:00
Andreas Zuber
fcf2d6a72c Support loading images from zip
This commit reenables the ability to load the images from a zip file
instead of a directory structure. It is possible to set the location of
this file via configforced.py
2019-10-02 17:01:14 +02:00
DarkPhoenix
843ced15bf Fix ammo damage ratios 2019-10-02 14:45:29 +03:00
DarkPhoenix
813db9340f Make sure to select right-clicked item on windows when Control is pressed 2019-10-02 13:19:46 +03:00
DarkPhoenix
acbd8a3298 Allow context menu-related batch actions to be triggered on ctrl too
Windows hides context menu when user presses alt
2019-10-02 12:44:27 +03:00
DarkPhoenix
561e22e894 Bump version 2019-10-02 12:11:25 +03:00
DarkPhoenix
05ac0a528a Show extra labels only when there's something inside 2019-10-02 10:56:01 +03:00
DarkPhoenix
c040353f6e Move some common functionality into common space of tab sizing method 2019-10-02 10:37:14 +03:00
DarkPhoenix
f23a8fa0c8 Adjust tab shadows to tab sizes 2019-10-02 10:33:11 +03:00
DarkPhoenix
ba93467646 Implement logic to have tabs of different sizes in additions pane and in fitting pane 2019-10-02 10:16:36 +03:00
DarkPhoenix
00d480860f Change tab outlook when option value is changed 2019-10-01 22:28:24 +03:00
DarkPhoenix
c94384acb8 Show amount of items hidden in additions tabs 2019-10-01 22:22:10 +03:00
DarkPhoenix
0c2c0ac6ef Add modules to session even when they were added via appendIgnoreEmpty 2019-10-01 10:25:31 +03:00
DarkPhoenix
61a33a331e Copy projection range when copying fit 2019-10-01 09:29:10 +03:00
DarkPhoenix
e374a6f2c6 Do not add null drains to not affect cap sim calculations 2019-09-30 17:27:10 +03:00
DarkPhoenix
dbd84dce28 Allow to change projected items' metas regardless of ability of target ship to fit them 2019-09-30 17:24:14 +03:00
DarkPhoenix
9d554f9c68 Make sure to do fit recalculation if it was changed after the last one 2019-09-30 17:20:22 +03:00
DarkPhoenix
576cf56735 Expose chance of jamming to stats pane itself rather than tooltip 2019-09-30 17:06:01 +03:00
DarkPhoenix
e2aaabbc16 Do not let jamming strength exceed 100% 2019-09-30 16:40:48 +03:00
DarkPhoenix
ef226898c0 Add projection range calculation to effects where it makes sense 2019-09-30 14:43:52 +03:00
DarkPhoenix
a0db235e5a Add support for projection range to bunch of effects 2019-09-30 03:17:21 +03:00
DarkPhoenix
5bf05ba775 Allow batch changes of projection range 2019-09-30 02:47:12 +03:00
DarkPhoenix
c073b1fa2a Do not show context menu on system-wide effects 2019-09-30 02:10:11 +03:00
DarkPhoenix
5f58307bf3 Add projection range commands to projected fighters 2019-09-30 02:04:24 +03:00
DarkPhoenix
8741b17a5e Add projection range commands for projected drones 2019-09-30 01:55:16 +03:00
DarkPhoenix
4c1fa09795 Apply drones and fighters from projected fit at range of 0 2019-09-30 01:21:39 +03:00
DarkPhoenix
ce7df2d01f Allow to change projection range for projected modules 2019-09-30 01:18:55 +03:00
DarkPhoenix
b433b0ea7c Change fit projection so that projection range actually counts for the sake of calculations overall
No effect support still
2019-09-30 00:19:31 +03:00
DarkPhoenix
20868d6b44 Add ability to change projection range of fits 2019-09-29 23:41:45 +03:00
DarkPhoenix
33103dbee9 Add column which shows projected item range 2019-09-29 22:16:19 +03:00
DarkPhoenix
2a05ac5a85 Pass projection range parameter to effects 2019-09-29 22:02:10 +03:00
DarkPhoenix
a013828128 Add projectionRange to actual objects built from database 2019-09-29 16:04:44 +03:00
DarkPhoenix
e19510b3d4 Move function which calculates range factor to eos 2019-09-29 16:00:37 +03:00
DarkPhoenix
390f2048f2 Add projection range column to projectable entities 2019-09-29 15:54:45 +03:00
DarkPhoenix
0bb732300e Do not rely on resistance view being available 2019-09-29 11:21:00 +03:00
DarkPhoenix
fd017df561 Add lock range limit support to ewar graph 2019-09-27 20:43:28 +03:00
DarkPhoenix
0ed16b9a6f Add lockrange support to DPS graphs 2019-09-27 20:19:29 +03:00
DarkPhoenix
865978fcc1 Add context menu which controls if graphs ignore drone control range or not, and add support for this option to RR graph 2019-09-27 18:40:33 +03:00
DarkPhoenix
a43f9930de Allow to change meta level of standup fighters 2019-09-23 16:44:26 +03:00
DarkPhoenix
c13cd23d54 Change parent of fit deletion dialog
Try out tip mentioned in https://github.com/wxWidgets/Phoenix/issues/1343
2019-09-23 15:32:16 +03:00
DarkPhoenix
ed1f52a114 Show implant description in tooltip of implant editor 2019-09-23 15:25:58 +03:00
DarkPhoenix
7dd063f04e Add graph setting to ignore drone control range 2019-09-17 13:03:21 +03:00
DarkPhoenix
6e9fc1d1d9 Do not crash on Nones in value 2019-09-11 08:32:13 +03:00
Alexander Maryanovsky
b2c718d614 Implemented copying the currently open fit stats to the clipboard. 2018-09-08 20:14:34 +03:00
149 changed files with 4024730 additions and 3557 deletions

View File

@@ -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
##########

View File

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

79
CONTRIBUTING.md Normal file
View 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*
![welcome](https://user-images.githubusercontent.com/54093496/66862580-d8edab00-ef99-11e9-94e2-e93d7043e620.png)
Login to GitHub, paste repo URL and seect the folder to which to clone the project, press *Clone*.
![Clone](https://user-images.githubusercontent.com/54093496/66862748-38e45180-ef9a-11e9-9f68-4903baf47385.png)
After process is complete, open *File* -> *Settings* -> *Project* -> *Project Interpreter*.
![Settings](https://user-images.githubusercontent.com/54093496/66862792-544f5c80-ef9a-11e9-9e0f-f64767f3f1b0.png)
Press on options and add new virtual environment.
![venv](https://user-images.githubusercontent.com/54093496/66862833-67622c80-ef9a-11e9-94fa-47cca0158d29.png)
Open project tree view and double-click on *requirements.txt*. Press *Install requirements*. Install all requirements.
![Reqs](https://user-images.githubusercontent.com/54093496/66862870-7a74fc80-ef9a-11e9-9b18-e64be42c49b8.png)
Create new *Run Configuration*. Set correct *Script path* and *Python interpreter*.
![Run configuraion](https://user-images.githubusercontent.com/54093496/66862970-b4460300-ef9a-11e9-9fb4-20e24759904b.png)
Test that the project is starting properly.

View File

@@ -2,20 +2,19 @@
[![Join us on Slack!](https://pyfainvite.azurewebsites.net/badge.svg)](https://pyfainvite.azurewebsites.net/) [![Build Status](https://travis-ci.org/pyfa-org/Pyfa.svg?branch=master)](https://travis-ci.org/pyfa-org/Pyfa)
![pyfa](https://cloud.githubusercontent.com/assets/3904767/10271512/af385ef2-6ade-11e5-8f67-52b8b1e4c797.PNG)
![pyfa](https://user-images.githubusercontent.com/275209/66119992-864be080-e5e2-11e9-994a-3a4368c9fad7.png)
## 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.

View File

@@ -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
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#======================================================================
# Copyright (C) 2012 Diego Duclos
#
@@ -18,35 +18,70 @@
# License along with eos. If not, see <http://www.gnu.org/licenses/>.
#======================================================================
import functools
import itertools
import json
import os
import sqlite3
import sys
# Add eos root path to sys.path so we can import ourselves
path = os.path.dirname(__file__)
sys.path.insert(0, os.path.realpath(os.path.join(path, '..')))
import json
import argparse
import itertools
ROOT_DIR = os.path.realpath(os.path.dirname(__file__))
DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
if ROOT_DIR not in sys.path:
sys.path.insert(0, ROOT_DIR)
GAMEDATA_SCHEMA_VERSION = 1
CATEGORIES_TO_REMOVE = [
30 # Apparel
]
def db_needs_update():
"""True if needs, false if it does not, none if we cannot check it."""
try:
with open(os.path.join(JSON_DIR, 'phobos', 'metadata.json')) as f:
data_version = next((r['field_value'] for r in json.load(f) if r['field_name'] == 'client_build'))
except KeyboardInterrupt:
raise
# If we have no source data - return None; should not update in this case
except:
return None
if not os.path.isfile(DB_PATH):
print('Gamedata DB not found')
return True
db_data_version = None
db_schema_version = None
try:
db = sqlite3.connect(DB_PATH)
cursor = db.cursor()
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'client_build\'')
for row in cursor:
db_data_version = int(row[0])
cursor.execute('SELECT field_value FROM metadata WHERE field_name = \'schema_version\'')
for row in cursor:
db_schema_version = int(row[0])
cursor.close()
db.close()
except KeyboardInterrupt:
raise
except:
print('Error when fetching gamedata DB metadata')
return True
if data_version != db_data_version:
print('Gamedata DB data version mismatch: needed {}, DB has {}'.format(data_version, db_data_version))
return True
if GAMEDATA_SCHEMA_VERSION != db_schema_version:
print('Gamedata DB schema version mismatch: needed {}, DB has {}'.format(GAMEDATA_SCHEMA_VERSION, db_schema_version))
return True
return False
def main(db, json_path):
if os.path.isfile(db):
os.remove(db)
jsonPath = os.path.expanduser(json_path)
def update_db():
# Import eos.config first and change it
import eos.config
eos.config.gamedata_connectionstring = db
eos.config.debug = False
print('Building gamedata DB...')
if os.path.isfile(DB_PATH):
os.remove(DB_PATH)
# Now thats done, we can import the eos modules using the config
import eos.db
import eos.gamedata
@@ -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
View 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)

View File

@@ -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")

View File

@@ -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")})

View File

@@ -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

View 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;")

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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()

View File

@@ -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,

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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):

View File

@@ -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):

View File

@@ -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

View File

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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View File

@@ -0,0 +1,3 @@
class DEFAULT:
"""Singleton class to signify default argument value."""
pass

BIN
eve.db

Binary file not shown.

View File

@@ -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']

View File

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

View File

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

View File

@@ -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

View File

@@ -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()

View File

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

View File

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

View File

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

View File

@@ -20,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)

View File

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

View File

@@ -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)

View File

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

View File

@@ -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()

View File

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

View File

@@ -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()

View File

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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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()

View 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()

View 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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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.",

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.",

View File

@@ -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)

View File

@@ -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.")

View File

@@ -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()

View File

@@ -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()

View File

@@ -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:

View File

@@ -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]

View File

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

View File

@@ -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

View 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()

View File

@@ -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:

View File

@@ -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)

View File

@@ -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()

View File

@@ -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):

View File

@@ -40,6 +40,7 @@ class CopySelectDialog(wx.Dialog):
copyFormatEsi = 3
copyFormatMultiBuy = 4
copyFormatEfs = 5
copyFormatFitStats = 6
def __init__(self, parent):
super().__init__(parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
@@ -50,7 +51,8 @@ class CopySelectDialog(wx.Dialog):
CopySelectDialog.copyFormatDna : self.exportDna,
CopySelectDialog.copyFormatEsi : self.exportEsi,
CopySelectDialog.copyFormatMultiBuy: self.exportMultiBuy,
CopySelectDialog.copyFormatEfs : self.exportEfs
CopySelectDialog.copyFormatEfs : self.exportEfs,
CopySelectDialog.copyFormatFitStats: self.exportFitStats
}
self.mainFrame = parent
@@ -62,6 +64,7 @@ class CopySelectDialog(wx.Dialog):
("ESI", (CopySelectDialog.copyFormatEsi, None)),
("DNA", (CopySelectDialog.copyFormatDna, DNA_OPTIONS)),
("EFS", (CopySelectDialog.copyFormatEfs, None)),
("Stats", (CopySelectDialog.copyFormatFitStats, None)),
# ("XML", (CopySelectDialog.copyFormatXml, None)),
))
@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -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