Compare commits
472 Commits
v2.8.0
...
v2.9.4dev3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ae59b2b5 | ||
|
|
51294f6cbc | ||
|
|
0439ace886 | ||
|
|
c85c735f9a | ||
|
|
c65b582497 | ||
|
|
7f2ac83e17 | ||
|
|
5ef2a40d1e | ||
|
|
5b52da737a | ||
|
|
51e8713cd6 | ||
|
|
c9b60f2c65 | ||
|
|
d777999af4 | ||
|
|
74444d56c4 | ||
|
|
a433c9638a | ||
|
|
672141cffc | ||
|
|
ac132cbb92 | ||
|
|
d9535b08b1 | ||
|
|
d93544b3bc | ||
|
|
2320c3cb57 | ||
|
|
bd5710c676 | ||
|
|
49f1412d91 | ||
|
|
54eea7d702 | ||
|
|
e26bcb2e5e | ||
|
|
7305c0a017 | ||
|
|
7d37b9e0e0 | ||
|
|
87f28db730 | ||
|
|
56d9a8b626 | ||
|
|
cb8f76c582 | ||
|
|
af0b7b92c7 | ||
|
|
9418b7a709 | ||
|
|
47c34f2186 | ||
|
|
2ca418c287 | ||
|
|
775e69305c | ||
|
|
0f1cbb4234 | ||
|
|
306710a314 | ||
|
|
776a4ee977 | ||
|
|
9dccfd756a | ||
|
|
15281ee6ce | ||
|
|
9a0dd6c521 | ||
|
|
a570f291ae | ||
|
|
cde7fdcaba | ||
|
|
e4780bc8ba | ||
|
|
4d35e5aee1 | ||
|
|
f7b705b9e2 | ||
|
|
48f44cdb0c | ||
|
|
013a2264c0 | ||
|
|
8222686dda | ||
|
|
7f2121e98d | ||
|
|
4b6c881dca | ||
|
|
5f9bf4a861 | ||
|
|
154db5df0b | ||
|
|
321b939d3a | ||
|
|
95a1d669f5 | ||
|
|
9e3c9bd056 | ||
|
|
bb9b3780ae | ||
|
|
4c976d9f35 | ||
|
|
52a1314803 | ||
|
|
a5475eb244 | ||
|
|
ba0a5db72f | ||
|
|
2bac4a954f | ||
|
|
e9f3453b04 | ||
|
|
c950592b5b | ||
|
|
1cd42669a0 | ||
|
|
2b24f14122 | ||
|
|
4932b685e1 | ||
|
|
cfffa1d99d | ||
|
|
44a7e53b9e | ||
|
|
b35bdd4e33 | ||
|
|
7f52f6fe44 | ||
|
|
34e49da0c1 | ||
|
|
5132698974 | ||
|
|
832cebcaaf | ||
|
|
4eaccd1eed | ||
|
|
5245f289a5 | ||
|
|
672aed44f2 | ||
|
|
8c890cf9a5 | ||
|
|
8f9a95db93 | ||
|
|
5a056e6d47 | ||
|
|
b121085271 | ||
|
|
b3ef55cb7f | ||
|
|
bbc8fd0f97 | ||
|
|
b11a576922 | ||
|
|
7375258b9f | ||
|
|
c447cf06d7 | ||
|
|
a7dcf1ace6 | ||
|
|
bd3d81e2f8 | ||
|
|
732b7a5ab9 | ||
|
|
3c16600c53 | ||
|
|
d0921ba9ec | ||
|
|
8ec840740b | ||
|
|
580ff1c435 | ||
|
|
d68296bfd7 | ||
|
|
c520b5e4f5 | ||
|
|
2da85888be | ||
|
|
bdd4a8cfb7 | ||
|
|
dbef3b8c84 | ||
|
|
63a6d746e8 | ||
|
|
7b8d9f8dbe | ||
|
|
7aa73e4b2f | ||
|
|
33883c562a | ||
|
|
36c17dde8d | ||
|
|
5a9fd188f0 | ||
|
|
4b65662c9f | ||
|
|
cd3d1a9557 | ||
|
|
9f63b0b412 | ||
|
|
9249ef24b3 | ||
|
|
63a599ca85 | ||
|
|
fa2bceaff9 | ||
|
|
e9dffeadf6 | ||
|
|
161c4629cf | ||
|
|
951ffcd35a | ||
|
|
ba21ebe058 | ||
|
|
f8c2604fb2 | ||
|
|
966763aaa4 | ||
|
|
4eb8973c31 | ||
|
|
72fe52e560 | ||
|
|
e346239174 | ||
|
|
dd27a26fa9 | ||
|
|
162b115c91 | ||
|
|
99f4ed6b33 | ||
|
|
53252241e1 | ||
|
|
698328e335 | ||
|
|
dece788f66 | ||
|
|
958fbac582 | ||
|
|
99d72956e8 | ||
|
|
eb601e991a | ||
|
|
cb7f0052c4 | ||
|
|
8b75b5f184 | ||
|
|
bf5935e033 | ||
|
|
67e80deed9 | ||
|
|
e39f9ffecf | ||
|
|
e865c9a399 | ||
|
|
a919510d07 | ||
|
|
bd86d3289f | ||
|
|
c8ff644b63 | ||
|
|
6703a08976 | ||
|
|
3d70d9e37c | ||
|
|
ef62d5cf97 | ||
|
|
caf5f33c80 | ||
|
|
29c29469c6 | ||
|
|
7b564f1f53 | ||
|
|
f280955ac3 | ||
|
|
e09fce8411 | ||
|
|
5777103d21 | ||
|
|
8671b20790 | ||
|
|
dc30b3ed1d | ||
|
|
07a9f77287 | ||
|
|
9d58ceb14d | ||
|
|
71c421702c | ||
|
|
989f12453d | ||
|
|
b7d6892d9f | ||
|
|
cfb351a751 | ||
|
|
dde1e7990d | ||
|
|
6e4ec54ac6 | ||
|
|
81da217a09 | ||
|
|
2d1613d8bc | ||
|
|
ccc2e6ece3 | ||
|
|
1206e95cfb | ||
|
|
921ccd3be9 | ||
|
|
178e3a3d56 | ||
|
|
5d255547e4 | ||
|
|
bd148b8792 | ||
|
|
b88ebdcfc0 | ||
|
|
3a26815d18 | ||
|
|
b70fcd9659 | ||
|
|
71aa557770 | ||
|
|
d35bf6514f | ||
|
|
abe015bee3 | ||
|
|
929520091f | ||
|
|
d4847112a9 | ||
|
|
4e2c3a3fcc | ||
|
|
91e6d89022 | ||
|
|
b9a71c08b7 | ||
|
|
070dd62e6d | ||
|
|
b404abca41 | ||
|
|
99f00b25a1 | ||
|
|
45936b5b98 | ||
|
|
b1aac9f56d | ||
|
|
13f370ceb9 | ||
|
|
b5a4f97cb5 | ||
|
|
0679a0af0f | ||
|
|
53fe3242b9 | ||
|
|
6615bed1cd | ||
|
|
ad0c7a7a9d | ||
|
|
87ba6a9af0 | ||
|
|
5c44df7f21 | ||
|
|
24bc675319 | ||
|
|
be2e0b5de4 | ||
|
|
e4481e8fb4 | ||
|
|
19b1eb161b | ||
|
|
30ed1ac81d | ||
|
|
b4288e17e5 | ||
|
|
c03d000c45 | ||
|
|
881ec8b5b4 | ||
|
|
71d5b28b75 | ||
|
|
a15fdc3b23 | ||
|
|
55cd33e653 | ||
|
|
af0b2b9f1b | ||
|
|
983641d1d5 | ||
|
|
4ab21e92bf | ||
|
|
413f00a475 | ||
|
|
bde2043294 | ||
|
|
d45857f1fc | ||
|
|
8a19bf78ce | ||
|
|
d523722988 | ||
|
|
031cb6fcfb | ||
|
|
72fc560241 | ||
|
|
1d7be66eb1 | ||
|
|
5d32a31dc3 | ||
|
|
4821bd1c72 | ||
|
|
6694caafa0 | ||
|
|
7b71c16cec | ||
|
|
df6e7b5772 | ||
|
|
7abc14eb7f | ||
|
|
4d21fa517a | ||
|
|
f2a82c31c4 | ||
|
|
337973965a | ||
|
|
f0b3aafd54 | ||
|
|
f6b97859aa | ||
|
|
1064a90a1c | ||
|
|
4c736de598 | ||
|
|
8d0ad26159 | ||
|
|
3efa07d821 | ||
|
|
44240c1d37 | ||
|
|
9eaeb60af7 | ||
|
|
cecb8f69a3 | ||
|
|
d402735c8b | ||
|
|
327ad78eb8 | ||
|
|
edc1ef0e38 | ||
|
|
c6bfd0bc05 | ||
|
|
3badab0353 | ||
|
|
8ca5b34c14 | ||
|
|
417ffd396c | ||
|
|
ccb0732f7d | ||
|
|
a994f55011 | ||
|
|
c2d309430e | ||
|
|
c3e1ec2760 | ||
|
|
8c40489049 | ||
|
|
051800bc16 | ||
|
|
d1a3e5c0e8 | ||
|
|
48d795676f | ||
|
|
3fec9ba173 | ||
|
|
264208b42e | ||
|
|
62e8da6ff2 | ||
|
|
e3f21cf700 | ||
|
|
fbc34224bc | ||
|
|
93cd3b97fa | ||
|
|
fda83bcb49 | ||
|
|
85b046a640 | ||
|
|
1177575f77 | ||
|
|
fc4a10efe3 | ||
|
|
f541b4329e | ||
|
|
7ba1a4c78f | ||
|
|
0675ed9a73 | ||
|
|
68a13a6bb8 | ||
|
|
61ef7c3487 | ||
|
|
a5bb16c460 | ||
|
|
e694ced86c | ||
|
|
babc2d1e42 | ||
|
|
8e717b19d9 | ||
|
|
dbca0f9dea | ||
|
|
7380244cd9 | ||
|
|
5b7c777d6b | ||
|
|
2fb9d3479f | ||
|
|
21f095250d | ||
|
|
270376e09c | ||
|
|
1ed71c6580 | ||
|
|
febc98045c | ||
|
|
72ecc62732 | ||
|
|
9e1681d3f9 | ||
|
|
0471ffa924 | ||
|
|
4269a00428 | ||
|
|
440b2caa8d | ||
|
|
7131cdbac6 | ||
|
|
b70c1a7c6c | ||
|
|
f9fd265280 | ||
|
|
9261d29ac1 | ||
|
|
df658d4950 | ||
|
|
69f68bf4ea | ||
|
|
614f3acc7e | ||
|
|
ec77acda1d | ||
|
|
603553517e | ||
|
|
4c04fd93ae | ||
|
|
f4f92b0821 | ||
|
|
3aede13136 | ||
|
|
d171548936 | ||
|
|
3d0db365af | ||
|
|
2716214816 | ||
|
|
91a46a2dd4 | ||
|
|
9fd70a4ba9 | ||
|
|
400bb10ed3 | ||
|
|
53915eb956 | ||
|
|
5457c31148 | ||
|
|
be9ac7cc9c | ||
|
|
3e2d21ea44 | ||
|
|
be374f4eb4 | ||
|
|
ab200ef74b | ||
|
|
3e1ef6e76d | ||
|
|
9fd0f016aa | ||
|
|
ae188826ae | ||
|
|
0736085676 | ||
|
|
fa4c48c6c2 | ||
|
|
82adccadda | ||
|
|
f100971fbe | ||
|
|
af2da86f6f | ||
|
|
5af211778b | ||
|
|
4c739308f9 | ||
|
|
98e834fd30 | ||
|
|
7ae8d1dc1a | ||
|
|
e57fce39fe | ||
|
|
fd54d2c28c | ||
|
|
b5c411349c | ||
|
|
4d31c8bb38 | ||
|
|
fbf3cace10 | ||
|
|
30b12b04e8 | ||
|
|
9301bb56fa | ||
|
|
bc4c35665e | ||
|
|
64bba0cfdb | ||
|
|
a270dc44d2 | ||
|
|
46fa1eb0c6 | ||
|
|
c7ed6367f9 | ||
|
|
9523c6f349 | ||
|
|
8b0f5f871c | ||
|
|
3fa5ac7858 | ||
|
|
4c0f88cdfa | ||
|
|
0fedb17586 | ||
|
|
960bef2b96 | ||
|
|
82777d0b02 | ||
|
|
a560597a85 | ||
|
|
59fb7bcd1b | ||
|
|
801f51b89c | ||
|
|
0b49bf201f | ||
|
|
cc8575b275 | ||
|
|
51a11edc36 | ||
|
|
4a6c1c3920 | ||
|
|
1cb0081420 | ||
|
|
d61d69188f | ||
|
|
a829efa7ff | ||
|
|
e6599d1a40 | ||
|
|
c4c68a4e93 | ||
|
|
f80244d560 | ||
|
|
a8684ef1b9 | ||
|
|
5ac9604fab | ||
|
|
700e249bf3 | ||
|
|
c2b742304e | ||
|
|
c2b0257449 | ||
|
|
0d90c187f2 | ||
|
|
88acec4241 | ||
|
|
9a6255cb29 | ||
|
|
c721869dfa | ||
|
|
1154435a89 | ||
|
|
6d9e60648e | ||
|
|
4522edb814 | ||
|
|
8139d88a52 | ||
|
|
353b845102 | ||
|
|
c3bffcad34 | ||
|
|
9e6031edf2 | ||
|
|
bb47df2119 | ||
|
|
aab9b39fea | ||
|
|
1ca4c73a3e | ||
|
|
7f870cf675 | ||
|
|
5f8ccb9831 | ||
|
|
c94acd9718 | ||
|
|
1733d6abb8 | ||
|
|
aa9f16d3ad | ||
|
|
c997661f3a | ||
|
|
6174bf8a0b | ||
|
|
36c19fe6e8 | ||
|
|
e6c9db3eef | ||
|
|
336af0f669 | ||
|
|
2800637a90 | ||
|
|
1fb9936893 | ||
|
|
d738ba615e | ||
|
|
e5a694384d | ||
|
|
1d98f889fd | ||
|
|
ad03f907fa | ||
|
|
5579929f83 | ||
|
|
e90e9a5ca1 | ||
|
|
2341650437 | ||
|
|
f2c26af791 | ||
|
|
fc82e45d6c | ||
|
|
1b54f07ce0 | ||
|
|
938fa11d13 | ||
|
|
a08aa77afc | ||
|
|
4cbeb34a3e | ||
|
|
d2fab0a10e | ||
|
|
a8c3612248 | ||
|
|
50e4e6fdcf | ||
|
|
69ada4f3ad | ||
|
|
3fc77d03b4 | ||
|
|
8072bb600c | ||
|
|
fda2c43a1d | ||
|
|
7fc98037a6 | ||
|
|
b51c8d8ecf | ||
|
|
dca2db5a6d | ||
|
|
fbb192404f | ||
|
|
7e41d8e20c | ||
|
|
9460998015 | ||
|
|
86056bf282 | ||
|
|
bacb374ea4 | ||
|
|
8d07f11aa4 | ||
|
|
b6a13b32de | ||
|
|
1f1a16f896 | ||
|
|
444d7af7ed | ||
|
|
e45e0b9444 | ||
|
|
33886aea1b | ||
|
|
1ddc2edd88 | ||
|
|
01b16ec5f0 | ||
|
|
ba337599c2 | ||
|
|
211a83defd | ||
|
|
091a7d7ab8 | ||
|
|
b3030bacf9 | ||
|
|
feb164a48e | ||
|
|
b3a556e70a | ||
|
|
9d322ca862 | ||
|
|
9bde08b1ce | ||
|
|
953a67e2c4 | ||
|
|
2435e08dc0 | ||
|
|
31645d20a5 | ||
|
|
5fa96c5e52 | ||
|
|
278d946f77 | ||
|
|
9b8aa67271 | ||
|
|
af197e56cf | ||
|
|
1d5ba89f2e | ||
|
|
f45ad5ceab | ||
|
|
5dae614fa1 | ||
|
|
c144c16a7f | ||
|
|
10425cd5d9 | ||
|
|
1d208a0cf7 | ||
|
|
db07f9d534 | ||
|
|
ca4eeed8d1 | ||
|
|
292d95f6f8 | ||
|
|
bd5ee32227 | ||
|
|
0a705d1d7f | ||
|
|
be1bd24a05 | ||
|
|
a9fb3501ac | ||
|
|
b8a8f9c422 | ||
|
|
6c29b3f38b | ||
|
|
f784f45b4e | ||
|
|
d59111eef5 | ||
|
|
99a2a38f9c | ||
|
|
768c1d37b1 | ||
|
|
b21cf5673f | ||
|
|
a6b8381e25 | ||
|
|
7d8768ca3e | ||
|
|
96e0a02d5c | ||
|
|
6a2bdade80 | ||
|
|
ea9eb3a8b7 | ||
|
|
9c5fe56981 | ||
|
|
8fed78d596 | ||
|
|
3c5846a983 | ||
|
|
fa0892032f | ||
|
|
ed7dd12258 | ||
|
|
1a5fc31e25 | ||
|
|
facb0b1023 | ||
|
|
6dd737cdd1 | ||
|
|
5d012d6959 | ||
|
|
b00dbbbf42 | ||
|
|
b269381818 | ||
|
|
fa72345bcf | ||
|
|
3229652efa | ||
|
|
c02cccf415 | ||
|
|
54277ebbda | ||
|
|
78c6d4f005 | ||
|
|
37ad2faa8e | ||
|
|
26cba1c47a | ||
|
|
f95420d7bf | ||
|
|
9c79fb666b | ||
|
|
a9a9fd1b1a | ||
|
|
253d3107a0 | ||
|
|
656a7fc784 | ||
|
|
ead8238d7e | ||
|
|
295368635b |
@@ -24,7 +24,7 @@ $ brew install Caskroom/cask/pyfa
|
||||
### Linux Distro-specific Packages
|
||||
The following is a list of pyfa packages available for certain distributions. Please note that these packages are maintained by third-parties and are not evaluated by the pyfa developers.
|
||||
|
||||
* Debian/Ubuntu/derivitives: https://github.com/AdamMajer/Pyfa/releases
|
||||
* Debian/Ubuntu/derivatives: https://github.com/AdamMajer/Pyfa/releases
|
||||
* Arch: https://aur.archlinux.org/packages/pyfa/
|
||||
* openSUSE: https://build.opensuse.org/package/show/home:rmk2/pyfa
|
||||
* FreeBSD: http://www.freshports.org/games/pyfa/ (see [#484](https://github.com/pyfa-org/Pyfa/issues/484) for instructions)
|
||||
@@ -53,6 +53,10 @@ pyfa is licensed under the GNU GPL v3.0, see LICENSE
|
||||
* [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
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -113,7 +113,7 @@ def defPaths(customSavePath=None):
|
||||
# Version data
|
||||
|
||||
with open(os.path.join(pyfaPath, "version.yml"), 'r') as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
data = yaml.load(file, Loader=yaml.SafeLoader)
|
||||
version = data['version']
|
||||
|
||||
# Where we store the saved fits etc, default is the current users home directory
|
||||
|
||||
@@ -29,7 +29,8 @@ added_files = [
|
||||
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes' # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
]
|
||||
|
||||
icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns")
|
||||
@@ -86,4 +87,4 @@ app = BUNDLE(
|
||||
'CFBundleDisplayName': 'pyfa',
|
||||
'CFBundleIdentifier': 'org.pyfaorg.pyfa',
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import yaml
|
||||
|
||||
|
||||
with open("version.yml", 'r') as file:
|
||||
data = yaml.load(file, Loader=yaml.FullLoader)
|
||||
data = yaml.load(file, Loader=yaml.SafeLoader)
|
||||
version = data['version']
|
||||
|
||||
os.environ["PYFA_DIST_DIR"] = os.path.join(os.getcwd(), 'dist')
|
||||
|
||||
@@ -17,7 +17,7 @@ added_files = [
|
||||
('../../imgs/gui/*.gif', 'imgs/gui'),
|
||||
('../../imgs/icons/*.png', 'imgs/icons'),
|
||||
('../../imgs/renders/*.png', 'imgs/renders'),
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../service/jargon/*.yaml', 'service/jargon'),
|
||||
('../../dist_assets/win/pyfa.ico', '.'),
|
||||
('../../dist_assets/win/pyfa.exe.manifest', '.'),
|
||||
('../../dist_assets/win/Microsoft.VC90.CRT.manifest', '.'),
|
||||
@@ -29,7 +29,8 @@ added_files = [
|
||||
]
|
||||
|
||||
import_these = [
|
||||
'numpy.core._dtype_ctypes' # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'numpy.core._dtype_ctypes', # https://github.com/pyinstaller/pyinstaller/issues/3982
|
||||
'sqlalchemy.ext.baked' # windows build doesn't launch without if when using sqlalchemy 1.3.x
|
||||
]
|
||||
|
||||
# Walk directories that do dynamic importing
|
||||
|
||||
@@ -93,3 +93,11 @@ class SpoolType(IntEnum):
|
||||
SCALE = 0 # [0..1]
|
||||
TIME = 1 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||
|
||||
|
||||
@unique
|
||||
class FitSystemSecurity(IntEnum):
|
||||
HISEC = 0
|
||||
LOWSEC = 1
|
||||
NULLSEC = 2
|
||||
WSPACE = 3
|
||||
|
||||
@@ -272,6 +272,15 @@ def getMarketGroup(lookfor, eager=None):
|
||||
return marketGroup
|
||||
|
||||
|
||||
def getMarketTreeNodeIds(rootNodeIds):
|
||||
allIds = set()
|
||||
addedIds = set(rootNodeIds)
|
||||
while addedIds:
|
||||
allIds.update(addedIds)
|
||||
addedIds = {mg.ID for mg in gamedata_session.query(MarketGroup).filter(MarketGroup.parentGroupID.in_(addedIds))}
|
||||
return allIds
|
||||
|
||||
|
||||
@cachedQuery(2, "where", "filter")
|
||||
def getItemsByCategory(filter, where=None, eager=None):
|
||||
if isinstance(filter, int):
|
||||
|
||||
15
eos/db/migrations/upgrade31.py
Normal file
15
eos/db/migrations/upgrade31.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
Migration 31
|
||||
|
||||
- added fit system security column
|
||||
"""
|
||||
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT systemSecurity FROM fits LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN systemSecurity INT")
|
||||
@@ -23,7 +23,7 @@ import datetime
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.db.saveddata.implant import charImplants_table
|
||||
from eos.effectHandlerHelpers import HandledImplantBoosterList, HandledSsoCharacterList
|
||||
from eos.effectHandlerHelpers import HandledImplantList, HandledSsoCharacterList
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.user import User
|
||||
from eos.saveddata.character import Character, Skill
|
||||
@@ -75,7 +75,7 @@ mapper(Character, characters_table,
|
||||
cascade="all,delete-orphan"),
|
||||
"_Character__implants" : relation(
|
||||
Implant,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
collection_class=HandledImplantList,
|
||||
cascade='all,delete-orphan',
|
||||
backref='character',
|
||||
single_parent=True,
|
||||
|
||||
@@ -31,7 +31,7 @@ from eos.db.saveddata.drone import drones_table
|
||||
from eos.db.saveddata.fighter import fighters_table
|
||||
from eos.db.saveddata.implant import fitImplants_table
|
||||
from eos.db.saveddata.module import modules_table
|
||||
from eos.effectHandlerHelpers import HandledDroneCargoList, HandledImplantBoosterList, HandledModuleList, HandledProjectedDroneList, HandledProjectedModList
|
||||
from eos.effectHandlerHelpers import HandledDroneCargoList, HandledImplantList, HandledBoosterList, HandledModuleList, HandledProjectedDroneList, HandledProjectedModList
|
||||
from eos.saveddata.booster import Booster
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from eos.saveddata.character import Character
|
||||
@@ -44,6 +44,7 @@ from eos.saveddata.module import Module
|
||||
from eos.saveddata.targetResists import TargetResists
|
||||
from eos.saveddata.user import User
|
||||
|
||||
|
||||
fits_table = Table("fits", saveddata_meta,
|
||||
Column("ID", Integer, primary_key=True),
|
||||
Column("ownerID", ForeignKey("users.ID"), nullable=True, index=True),
|
||||
@@ -59,7 +60,8 @@ fits_table = Table("fits", saveddata_meta,
|
||||
Column("notes", String, nullable=True),
|
||||
Column("ignoreRestrictions", Boolean, default=0),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now)
|
||||
Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now),
|
||||
Column("systemSecurity", Integer, nullable=True)
|
||||
)
|
||||
|
||||
projectedFits_table = Table("projectedFits", saveddata_meta,
|
||||
@@ -183,7 +185,7 @@ mapper(es_Fit, fits_table,
|
||||
"shipID": fits_table.c.shipID,
|
||||
"_Fit__boosters": relation(
|
||||
Booster,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
collection_class=HandledBoosterList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
backref='owner',
|
||||
single_parent=True),
|
||||
@@ -219,7 +221,7 @@ mapper(es_Fit, fits_table,
|
||||
primaryjoin=and_(fighters_table.c.fitID == fits_table.c.ID, fighters_table.c.projected == True)), # noqa
|
||||
"_Fit__implants": relation(
|
||||
Implant,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
collection_class=HandledImplantList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
backref='owner',
|
||||
single_parent=True,
|
||||
|
||||
@@ -23,7 +23,7 @@ import datetime
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.db.saveddata.implant import implantsSetMap_table
|
||||
from eos.effectHandlerHelpers import HandledImplantBoosterList
|
||||
from eos.effectHandlerHelpers import HandledImplantList
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.implantSet import ImplantSet
|
||||
|
||||
@@ -38,7 +38,7 @@ mapper(ImplantSet, implant_set_table,
|
||||
properties={
|
||||
"_ImplantSet__implants": relation(
|
||||
Implant,
|
||||
collection_class=HandledImplantBoosterList,
|
||||
collection_class=HandledImplantList,
|
||||
cascade='all, delete, delete-orphan',
|
||||
backref='set',
|
||||
single_parent=True,
|
||||
|
||||
@@ -38,49 +38,58 @@ class DefaultDatabaseValues(object):
|
||||
["[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]Concussion Bomb", "0", "0", "6400", "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][T2] Scorch", "72", "16", "0", "0"],
|
||||
["[Frequency Crystals][T2] Conflagration", "61.6", "61.6", "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]Multifrequency", "61.6", "44", "0", "0"],
|
||||
["[Frequency Crystals]Radio", "44", "0", "0", "0"],
|
||||
["[Frequency Crystals]Standard", "44", "26.4", "0", "0"],
|
||||
["[Frequency Crystals]Ultraviolet", "52.8", "26.4", "0", "0"],
|
||||
["[Frequency Crystals]Xray", "52.8", "35.2", "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]Iridium", "0", "28.8", "38.4", "0"],
|
||||
["[Hybrid Charges]Iron", "0", "19.2", "28.8", "0"],
|
||||
["[Hybrid Charges]Lead", "0", "28.8", "48", "0"],
|
||||
["[Hybrid Charges]Plutonium", "0", "48", "57.6", "0"],
|
||||
["[Hybrid Charges]Thorium", "0", "38.4", "48", "0"],
|
||||
["[Hybrid Charges]Tungsten", "0", "19.2", "38.4", "0"],
|
||||
["[Hybrid Charges]Uranium", "0", "38.4", "57.6", "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"],
|
||||
["[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] Tremor", "0", "0", "24", "40"],
|
||||
["[Projectile Ammo][T2] Quake", "0", "0", "40", "72"],
|
||||
["[Projectile Ammo][T2] Hail", "0", "0", "26.4", "96.8"],
|
||||
["[Projectile Ammo][T2] Barrage", "0", "0", "40", "48"],
|
||||
["[Projectile Ammo]Carbonized Lead", "0", "0", "35.2", "8.8"],
|
||||
["[Projectile Ammo]Depleted Uranium", "0", "26.4", "17.6", "26.4"],
|
||||
["[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]Fusion", "0", "0", "17.6", "88"],
|
||||
["[Projectile Ammo]Nuclear", "0", "0", "8.8", "35.2"],
|
||||
["[Projectile Ammo]Phased Plasma", "0", "88", "17.6", "0"],
|
||||
["[Projectile Ammo]Proton", "26.4", "0", "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"],
|
||||
# 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"],
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
from logbook import Logger
|
||||
from utils.deprecated import deprecated
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -112,6 +113,11 @@ class HandledList(list):
|
||||
thing.itemID = 0
|
||||
list.remove(self, thing)
|
||||
|
||||
def sort(self, *args, **kwargs):
|
||||
# We need it here to prevent external users from accidentally sorting the list as alot of
|
||||
# external logic relies on keeping modules at their places
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class HandledModuleList(HandledList):
|
||||
|
||||
@@ -125,76 +131,89 @@ class HandledModuleList(HandledList):
|
||||
emptyPosition = currPos
|
||||
|
||||
if emptyPosition < len(self):
|
||||
del self[emptyPosition]
|
||||
mod.position = emptyPosition
|
||||
HandledList.insert(self, emptyPosition, mod)
|
||||
self.__toModule(emptyPosition, mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
return
|
||||
|
||||
self.appendIgnoreEmpty(mod)
|
||||
self.__toDummy(mod.position)
|
||||
else:
|
||||
self.appendIgnoreEmpty(mod)
|
||||
|
||||
def appendIgnoreEmpty(self, mod):
|
||||
mod.position = len(self)
|
||||
HandledList.append(self, mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
|
||||
def replace(self, idx, mod):
|
||||
try:
|
||||
oldMod = self[idx]
|
||||
except IndexError:
|
||||
return
|
||||
self.__toModule(idx, mod)
|
||||
if mod.isInvalid:
|
||||
self.__toModule(idx, oldMod)
|
||||
|
||||
def replaceRackPosition(self, rackPosition, mod):
|
||||
listPositions = []
|
||||
for currMod in self:
|
||||
for currPos in range(len(self)):
|
||||
currMod = self[currPos]
|
||||
if currMod.slot == mod.slot:
|
||||
listPositions.append(currMod.position)
|
||||
listPositions.append(currPos)
|
||||
listPositions.sort()
|
||||
try:
|
||||
modListPosition = listPositions[rackPosition]
|
||||
except IndexError:
|
||||
self.appendIgnoreEmpty(mod)
|
||||
else:
|
||||
self.toDummy(modListPosition)
|
||||
if not mod.isEmpty:
|
||||
self.toModule(modListPosition, mod)
|
||||
oldMod = self[modListPosition]
|
||||
if mod.isEmpty:
|
||||
self.__toDummy(modListPosition)
|
||||
else:
|
||||
self.__toModule(modListPosition, mod)
|
||||
# If new module cannot be appended, restore old state
|
||||
if mod.isInvalid:
|
||||
self.toDummy(modListPosition)
|
||||
if oldMod.isEmpty:
|
||||
self.__toDummy(modListPosition)
|
||||
else:
|
||||
self.__toModule(modListPosition, oldMod)
|
||||
|
||||
def insert(self, index, mod):
|
||||
mod.position = index
|
||||
i = index
|
||||
def insert(self, idx, mod):
|
||||
mod.position = idx
|
||||
i = idx
|
||||
while i < len(self):
|
||||
self[i].position += 1
|
||||
i += 1
|
||||
HandledList.insert(self, index, mod)
|
||||
HandledList.insert(self, idx, mod)
|
||||
if mod.isInvalid:
|
||||
self.remove(mod)
|
||||
|
||||
def remove(self, mod):
|
||||
HandledList.remove(self, mod)
|
||||
oldPos = mod.position
|
||||
|
||||
mod.position = None
|
||||
for i in range(oldPos, len(self)):
|
||||
self[i].position -= 1
|
||||
|
||||
def toDummy(self, index):
|
||||
def free(self, idx):
|
||||
self.__toDummy(idx)
|
||||
|
||||
def __toDummy(self, index):
|
||||
mod = self[index]
|
||||
if not mod.isEmpty:
|
||||
dummy = mod.buildEmpty(mod.slot)
|
||||
dummy.position = index
|
||||
self[index] = dummy
|
||||
mod.position = None
|
||||
|
||||
def toModule(self, index, mod):
|
||||
def __toModule(self, index, mod):
|
||||
oldMod = self[index]
|
||||
mod.position = index
|
||||
self[index] = mod
|
||||
|
||||
@deprecated
|
||||
def freeSlot(self, slot):
|
||||
for i in range(len(self)):
|
||||
mod = self[i]
|
||||
if mod.getModifiedItemAttr("subSystemSlot") == slot:
|
||||
self.toDummy(i)
|
||||
break
|
||||
oldMod.position = None
|
||||
|
||||
|
||||
class HandledDroneCargoList(HandledList):
|
||||
|
||||
def find(self, item):
|
||||
for o in self:
|
||||
if o.item == item:
|
||||
@@ -206,35 +225,99 @@ class HandledDroneCargoList(HandledList):
|
||||
|
||||
def append(self, thing):
|
||||
HandledList.append(self, thing)
|
||||
if thing.isInvalid:
|
||||
self.remove(thing)
|
||||
|
||||
def insert(self, idx, thing):
|
||||
HandledList.insert(self, idx, thing)
|
||||
if thing.isInvalid:
|
||||
self.remove(thing)
|
||||
|
||||
|
||||
class HandledImplantBoosterList(HandledList):
|
||||
def append(self, thing):
|
||||
if thing.isInvalid:
|
||||
HandledList.append(self, thing)
|
||||
self.remove(thing)
|
||||
class HandledImplantList(HandledList):
|
||||
|
||||
def append(self, implant):
|
||||
if implant.isInvalid:
|
||||
HandledList.append(self, implant)
|
||||
self.remove(implant)
|
||||
return
|
||||
if self.__slotCheck(implant):
|
||||
HandledList.append(self, implant)
|
||||
self.remove(implant)
|
||||
return
|
||||
HandledList.append(self, implant)
|
||||
|
||||
self.makeRoom(thing)
|
||||
HandledList.append(self, thing)
|
||||
def insert(self, idx, implant):
|
||||
if implant.isInvalid:
|
||||
HandledList.insert(self, idx, implant)
|
||||
self.remove(implant)
|
||||
return
|
||||
if self.__slotCheck(implant):
|
||||
HandledList.insert(self, idx, implant)
|
||||
self.remove(implant)
|
||||
return
|
||||
HandledList.insert(self, idx, implant)
|
||||
|
||||
def makeRoom(self, thing):
|
||||
def makeRoom(self, implant):
|
||||
# if needed, remove booster that was occupying slot
|
||||
oldObj = next((m for m in self if m.slot == thing.slot), None)
|
||||
if oldObj:
|
||||
pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", thing.slot, oldObj.item.name,
|
||||
thing.item.name)
|
||||
itemID = oldObj.itemID
|
||||
oldObj = next((i for i in self if i.slot == implant.slot), None)
|
||||
if oldObj is not None:
|
||||
pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", implant.slot, oldObj.item.name, implant.item.name)
|
||||
position = self.index(oldObj)
|
||||
from gui.fitCommands.helpers import ImplantInfo
|
||||
implantInfo = ImplantInfo.fromImplant(oldObj)
|
||||
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
|
||||
self.remove(oldObj)
|
||||
return itemID
|
||||
return None
|
||||
return position, implantInfo
|
||||
return None, None
|
||||
|
||||
def __slotCheck(self, implant):
|
||||
return any(i.slot == implant.slot for i in self)
|
||||
|
||||
|
||||
class HandledBoosterList(HandledList):
|
||||
|
||||
def append(self, booster):
|
||||
if booster.isInvalid:
|
||||
HandledList.append(self, booster)
|
||||
self.remove(booster)
|
||||
return
|
||||
if self.__slotCheck(booster):
|
||||
HandledList.append(self, booster)
|
||||
self.remove(booster)
|
||||
return
|
||||
HandledList.append(self, booster)
|
||||
|
||||
def insert(self, idx, booster):
|
||||
if booster.isInvalid:
|
||||
HandledList.insert(self, idx, booster)
|
||||
self.remove(booster)
|
||||
return
|
||||
if self.__slotCheck(booster):
|
||||
HandledList.insert(self, idx, booster)
|
||||
self.remove(booster)
|
||||
return
|
||||
HandledList.insert(self, idx, booster)
|
||||
|
||||
def makeRoom(self, booster):
|
||||
# if needed, remove booster that was occupying slot
|
||||
oldObj = next((b for b in self if b.slot == booster.slot), None)
|
||||
if oldObj is not None:
|
||||
pyfalog.info("Slot {0} occupied with {1}, replacing with {2}", booster.slot, oldObj.item.name, booster.item.name)
|
||||
position = self.index(oldObj)
|
||||
from gui.fitCommands.helpers import BoosterInfo
|
||||
boosterInfo = BoosterInfo.fromBooster(oldObj)
|
||||
oldObj.itemID = 0 # hack to remove from DB. See GH issue #324
|
||||
self.remove(oldObj)
|
||||
return position, boosterInfo
|
||||
return None, None
|
||||
|
||||
def __slotCheck(self, booster):
|
||||
return any(b.slot == booster.slot for b in self)
|
||||
|
||||
|
||||
class HandledSsoCharacterList(list):
|
||||
|
||||
def append(self, character):
|
||||
old = next((x for x in self if x.client == character.client), None)
|
||||
if old is not None:
|
||||
@@ -245,6 +328,7 @@ class HandledSsoCharacterList(list):
|
||||
|
||||
|
||||
class HandledProjectedModList(HandledList):
|
||||
|
||||
def append(self, proj):
|
||||
if proj.isInvalid:
|
||||
# we must include it before we remove it. doing it this way ensures
|
||||
@@ -252,14 +336,21 @@ class HandledProjectedModList(HandledList):
|
||||
HandledList.append(self, proj)
|
||||
self.remove(proj)
|
||||
return
|
||||
|
||||
proj.projected = True
|
||||
|
||||
if proj.isExclusiveSystemEffect:
|
||||
self.makeRoom(proj)
|
||||
|
||||
HandledList.append(self, proj)
|
||||
# Remove non-projectable modules
|
||||
if not proj.item.isType("projected") and not proj.isExclusiveSystemEffect:
|
||||
self.remove(proj)
|
||||
|
||||
def insert(self, idx, proj):
|
||||
if proj.isInvalid:
|
||||
# we must include it before we remove it. doing it this way ensures
|
||||
# rows and relationships in database are removed as well
|
||||
HandledList.insert(self, idx, proj)
|
||||
self.remove(proj)
|
||||
return
|
||||
proj.projected = True
|
||||
HandledList.insert(self, idx, proj)
|
||||
# Remove non-projectable modules
|
||||
if not proj.item.isType("projected") and not proj.isExclusiveSystemEffect:
|
||||
self.remove(proj)
|
||||
@@ -269,24 +360,38 @@ class HandledProjectedModList(HandledList):
|
||||
return next((m for m in self if m.isExclusiveSystemEffect), None)
|
||||
|
||||
def makeRoom(self, proj):
|
||||
# remove other system effects - only 1 per fit plz
|
||||
oldEffect = self.currentSystemEffect
|
||||
if proj.isExclusiveSystemEffect:
|
||||
# remove other system effects - only 1 per fit plz
|
||||
mod = self.currentSystemEffect
|
||||
|
||||
if oldEffect:
|
||||
pyfalog.info("System effect occupied with {0}, replacing with {1}", oldEffect.item.name, proj.item.name)
|
||||
self.remove(oldEffect)
|
||||
return oldEffect.itemID
|
||||
return None
|
||||
if mod:
|
||||
pyfalog.info("System effect occupied with {0}, removing it to make space for {1}".format(mod.item.name, proj.item.name))
|
||||
position = self.index(mod)
|
||||
# We need to pack up this info, so whatever...
|
||||
from gui.fitCommands.helpers import ModuleInfo
|
||||
modInfo = ModuleInfo.fromModule(mod)
|
||||
self.remove(mod)
|
||||
return position, modInfo
|
||||
return None, None
|
||||
|
||||
|
||||
class HandledProjectedDroneList(HandledDroneCargoList):
|
||||
|
||||
def append(self, proj):
|
||||
proj.projected = True
|
||||
HandledList.append(self, proj)
|
||||
|
||||
# Remove invalid or non-projectable drones
|
||||
if proj.isInvalid or not proj.item.isType("projected"):
|
||||
self.remove(proj)
|
||||
proj.projected = False
|
||||
|
||||
def insert(self, idx, proj):
|
||||
proj.projected = True
|
||||
HandledList.insert(self, idx, proj)
|
||||
# Remove invalid or non-projectable drones
|
||||
if proj.isInvalid or not proj.item.isType("projected"):
|
||||
self.remove(proj)
|
||||
proj.projected = False
|
||||
|
||||
|
||||
class HandledItem(object):
|
||||
|
||||
585
eos/effects.py
585
eos/effects.py
@@ -19,7 +19,7 @@
|
||||
|
||||
|
||||
import eos.config
|
||||
from eos.const import FittingModuleState
|
||||
from eos.const import FittingModuleState, FitSystemSecurity
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, resolveSpoolOptions
|
||||
|
||||
|
||||
@@ -1719,7 +1719,7 @@ class Effect596(BaseEffect):
|
||||
ammoInfluenceRange
|
||||
|
||||
Used by:
|
||||
Items from category: Charge (587 of 949)
|
||||
Items from category: Charge (587 of 951)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -1734,9 +1734,9 @@ class Effect598(BaseEffect):
|
||||
ammoSpeedMultiplier
|
||||
|
||||
Used by:
|
||||
Charges from group: Festival Charges (26 of 26)
|
||||
Charges from group: Festival Charges (28 of 28)
|
||||
Charges from group: Interdiction Probe (2 of 2)
|
||||
Items from market group: Special Edition Assets > Special Edition Festival Assets (30 of 33)
|
||||
Items from market group: Special Edition Assets > Special Edition Festival Assets (32 of 35)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -1771,7 +1771,7 @@ class Effect600(BaseEffect):
|
||||
ammoTrackingMultiplier
|
||||
|
||||
Used by:
|
||||
Items from category: Charge (182 of 949)
|
||||
Items from category: Charge (182 of 951)
|
||||
Charges from group: Projectile Ammo (128 of 128)
|
||||
"""
|
||||
|
||||
@@ -2298,7 +2298,7 @@ class Effect804(BaseEffect):
|
||||
ammoInfluenceCapNeed
|
||||
|
||||
Used by:
|
||||
Items from category: Charge (493 of 949)
|
||||
Items from category: Charge (493 of 951)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -6739,7 +6739,7 @@ class Effect2302(BaseEffect):
|
||||
damageControl
|
||||
|
||||
Used by:
|
||||
Modules from group: Damage Control (22 of 27)
|
||||
Modules from group: Damage Control (24 of 29)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -7758,7 +7758,7 @@ class Effect2737(BaseEffect):
|
||||
boosterShieldCapacityPenalty
|
||||
|
||||
Used by:
|
||||
Implants from group: Booster (12 of 70)
|
||||
Implants from group: Booster (12 of 67)
|
||||
"""
|
||||
|
||||
attr = 'boosterShieldCapacityPenalty'
|
||||
@@ -9050,7 +9050,7 @@ class Effect3001(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Modules from group: Missile Launcher Torpedo (22 of 22)
|
||||
Items from market group: Ship Equipment > Turrets & Bays (429 of 883)
|
||||
Items from market group: Ship Equipment > Turrets & Bays (429 of 888)
|
||||
Module: Interdiction Sphere Launcher I
|
||||
"""
|
||||
|
||||
@@ -9113,7 +9113,7 @@ class Effect3025(BaseEffect):
|
||||
Used by:
|
||||
Modules from group: Energy Weapon (101 of 214)
|
||||
Modules from group: Hybrid Weapon (105 of 221)
|
||||
Modules from group: Precursor Weapon (15 of 15)
|
||||
Modules from group: Precursor Weapon (18 of 18)
|
||||
Modules from group: Projectile Weapon (99 of 165)
|
||||
"""
|
||||
|
||||
@@ -10027,16 +10027,16 @@ class Effect3380(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context):
|
||||
|
||||
if 'projected' in context:
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', module.getModifiedItemAttr('warpScrambleStrength'))
|
||||
if module.charge is not None and module.charge.ID == 45010:
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.item.requiresSkill('High Speed Maneuvering') and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill('Micro Jump Drive Operation') and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if module.charge is not None:
|
||||
if module.charge.ID in (29003, 45010):
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', module.getModifiedItemAttr('warpScrambleStrength'))
|
||||
if module.charge.ID == 45010:
|
||||
fit.modules.filteredItemIncrease(
|
||||
lambda mod: mod.item.requiresSkill('High Speed Maneuvering') or mod.item.requiresSkill('Micro Jump Drive Operation'),
|
||||
'activationBlocked', 1)
|
||||
else:
|
||||
fit.ship.forceItemAttr('disallowAssistance', 1)
|
||||
if module.charge is None:
|
||||
fit.ship.boostItemAttr('mass', module.getModifiedItemAttr('massBonusPercentage'))
|
||||
fit.ship.boostItemAttr('signatureRadius', module.getModifiedItemAttr('signatureRadiusBonus'))
|
||||
@@ -10045,8 +10045,6 @@ class Effect3380(BaseEffect):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Propulsion Module',
|
||||
'speedFactor', module.getModifiedItemAttr('speedFactorBonus'))
|
||||
|
||||
fit.ship.forceItemAttr('disallowAssistance', 1)
|
||||
|
||||
|
||||
class Effect3392(BaseEffect):
|
||||
"""
|
||||
@@ -15226,7 +15224,6 @@ class Effect4575(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, src, context):
|
||||
fit.extraAttributes['siege'] = True
|
||||
fit.ship.boostItemAttr('maxVelocity', src.getModifiedItemAttr('speedFactor'), stackingPenalties=True)
|
||||
fit.ship.multiplyItemAttr('mass', src.getModifiedItemAttr('siegeMassMultiplier'))
|
||||
fit.ship.multiplyItemAttr('scanResolution',
|
||||
@@ -15235,96 +15232,57 @@ class Effect4575(BaseEffect):
|
||||
|
||||
# Remote Shield Repper Bonuses
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
|
||||
'duration',
|
||||
src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'),
|
||||
)
|
||||
'duration', src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
|
||||
'maxRange',
|
||||
src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
|
||||
stackingPenalties=True
|
||||
)
|
||||
'maxRange', src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
|
||||
stackingPenalties=True)
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
|
||||
'capacitorNeed',
|
||||
src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus')
|
||||
)
|
||||
'capacitorNeed', src.getModifiedItemAttr('industrialCoreRemoteLogisticsDurationBonus'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Emission Systems'),
|
||||
'falloffEffectiveness',
|
||||
src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
|
||||
stackingPenalties=True
|
||||
)
|
||||
'falloffEffectiveness', src.getModifiedItemAttr('industrialCoreRemoteLogisticsRangeBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Local Shield Repper Bonuses
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Operation'),
|
||||
'duration',
|
||||
src.getModifiedItemAttr('industrialCoreLocalLogisticsDurationBonus'),
|
||||
)
|
||||
'duration', src.getModifiedItemAttr('industrialCoreLocalLogisticsDurationBonus'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Operation'),
|
||||
'shieldBonus',
|
||||
src.getModifiedItemAttr('industrialCoreLocalLogisticsAmountBonus'),
|
||||
stackingPenalties=True
|
||||
)
|
||||
'shieldBonus', src.getModifiedItemAttr('industrialCoreLocalLogisticsAmountBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Mining Burst Bonuses
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
|
||||
'warfareBuff1Value',
|
||||
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
|
||||
)
|
||||
|
||||
'warfareBuff1Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
|
||||
'warfareBuff2Value',
|
||||
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
|
||||
)
|
||||
|
||||
'warfareBuff2Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
|
||||
'warfareBuff3Value',
|
||||
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
|
||||
)
|
||||
|
||||
'warfareBuff3Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining Foreman'),
|
||||
'warfareBuff4Value',
|
||||
src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'),
|
||||
)
|
||||
'warfareBuff4Value', src.getModifiedItemAttr('industrialCoreBonusMiningBurstStrength'))
|
||||
|
||||
# Command Burst Range Bonus
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Leadership'),
|
||||
'maxRange',
|
||||
src.getModifiedItemAttr('industrialCoreBonusCommandBurstRange'),
|
||||
stackingPenalties=True
|
||||
)
|
||||
'maxRange', src.getModifiedItemAttr('industrialCoreBonusCommandBurstRange'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Drone Bonuses
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Ice Harvesting Drone Operation'),
|
||||
'duration',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneIceHarvesting'),
|
||||
)
|
||||
'duration', src.getModifiedItemAttr('industrialCoreBonusDroneIceHarvesting'))
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Mining Drone Operation'),
|
||||
'miningAmount',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneMining'),
|
||||
)
|
||||
'miningAmount', src.getModifiedItemAttr('industrialCoreBonusDroneMining'))
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'maxVelocity',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneVelocity'),
|
||||
)
|
||||
'maxVelocity', src.getModifiedItemAttr('industrialCoreBonusDroneVelocity'),
|
||||
stackingPenalties=True)
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'damageMultiplier', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
|
||||
stackingPenalties=True)
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'shieldCapacity', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'armorHP', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'hp', src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'))
|
||||
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'damageMultiplier',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
|
||||
stackingPenalties=True
|
||||
)
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'shieldCapacity',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
|
||||
)
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'armorHP',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
|
||||
)
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'hp',
|
||||
src.getModifiedItemAttr('industrialCoreBonusDroneDamageHP'),
|
||||
)
|
||||
|
||||
# Todo: remote impedance (no reps, etc)
|
||||
# Remote impedance (no reps, etc)
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', src.getModifiedItemAttr('siegeModeWarpStatus'))
|
||||
fit.ship.boostItemAttr('remoteRepairImpedance', src.getModifiedItemAttr('remoteRepairImpedanceBonus'))
|
||||
fit.ship.increaseItemAttr('disallowTethering', src.getModifiedItemAttr('disallowTethering'))
|
||||
@@ -16288,9 +16246,9 @@ class Effect4902(BaseEffect):
|
||||
MWDSignatureRadiusRoleBonus
|
||||
|
||||
Used by:
|
||||
Ships from group: Assault Frigate (8 of 12)
|
||||
Ships from group: Command Destroyer (4 of 4)
|
||||
Ships from group: Heavy Assault Cruiser (8 of 11)
|
||||
Ships from group: Assault Frigate (9 of 13)
|
||||
Ships from group: Command Destroyer (5 of 5)
|
||||
Ships from group: Heavy Assault Cruiser (9 of 12)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -19964,22 +19922,6 @@ class Effect5389(BaseEffect):
|
||||
'trackingSpeed', ship.getModifiedItemAttr('shipBonusGC'), skill='Gallente Cruiser')
|
||||
|
||||
|
||||
class Effect5390(BaseEffect):
|
||||
"""
|
||||
shipBonusDroneMWDboostGC
|
||||
|
||||
Used by:
|
||||
Ship: Vexor Navy Issue
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'maxVelocity', ship.getModifiedItemAttr('shipBonusGC'), skill='Gallente Cruiser')
|
||||
|
||||
|
||||
class Effect5397(BaseEffect):
|
||||
"""
|
||||
baseMaxScanDeviationModifierModuleOnline2None
|
||||
@@ -22476,7 +22418,6 @@ class Effect5822(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Chameleon
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -22493,7 +22434,6 @@ class Effect5823(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Chameleon
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -22510,7 +22450,6 @@ class Effect5824(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Ship: Chameleon
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -23414,16 +23353,9 @@ class Effect5934(BaseEffect):
|
||||
return
|
||||
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', module.getModifiedItemAttr('warpScrambleStrength'))
|
||||
|
||||
# this is such a dirty hack
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.state > FittingModuleState.ONLINE and (
|
||||
mod.item.requiresSkill('Micro Jump Drive Operation') or
|
||||
mod.item.requiresSkill('High Speed Maneuvering')
|
||||
):
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill('Micro Jump Drive Operation') and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
fit.modules.filteredItemIncrease(
|
||||
lambda mod: mod.item.requiresSkill('High Speed Maneuvering') or mod.item.requiresSkill('Micro Jump Drive Operation'),
|
||||
'activationBlocked', module.getModifiedItemAttr('activationBlockedStrenght'))
|
||||
|
||||
|
||||
class Effect5938(BaseEffect):
|
||||
@@ -25259,7 +25191,7 @@ class Effect6214(BaseEffect):
|
||||
roleBonusCDLinksPGReduction
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (4 of 4)
|
||||
Ships from group: Command Destroyer (5 of 5)
|
||||
Ship: Porpoise
|
||||
"""
|
||||
|
||||
@@ -25312,12 +25244,9 @@ class Effect6222(BaseEffect):
|
||||
def handler(fit, module, context):
|
||||
if 'projected' in context:
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', module.getModifiedItemAttr('warpScrambleStrength'))
|
||||
if module.charge is not None and module.charge.ID == 47336:
|
||||
for mod in fit.modules:
|
||||
if not mod.isEmpty and mod.item.requiresSkill('High Speed Maneuvering') and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
if not mod.isEmpty and mod.item.requiresSkill('Micro Jump Drive Operation') and mod.state > FittingModuleState.ONLINE:
|
||||
mod.state = FittingModuleState.ONLINE
|
||||
fit.modules.filteredItemIncrease(
|
||||
lambda mod: mod.item.requiresSkill('High Speed Maneuvering') or mod.item.requiresSkill('Micro Jump Drive Operation'),
|
||||
'activationBlocked', module.getModifiedItemAttr('activationBlockedStrenght'))
|
||||
|
||||
|
||||
class Effect6230(BaseEffect):
|
||||
@@ -25844,8 +25773,7 @@ class Effect6315(BaseEffect):
|
||||
eliteBonusCommandDestroyerSkirmish1
|
||||
|
||||
Used by:
|
||||
Ship: Bifrost
|
||||
Ship: Magus
|
||||
Ships from group: Command Destroyer (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -25894,7 +25822,7 @@ class Effect6317(BaseEffect):
|
||||
eliteBonusCommandDestroyerMJFGspool2
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (4 of 4)
|
||||
Ships from group: Command Destroyer (5 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -26167,8 +26095,7 @@ class Effect6334(BaseEffect):
|
||||
eliteBonusCommandDestroyerInfo1
|
||||
|
||||
Used by:
|
||||
Ship: Pontifex
|
||||
Ship: Stork
|
||||
Ships from group: Command Destroyer (3 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -28928,7 +28855,7 @@ class Effect6581(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, src, context):
|
||||
# Remote effect bonuses (duration / amount / range / fallout)
|
||||
# Remote effect bonuses (duration / amount / range / falloff)
|
||||
for skill, amtAttr, stack in (
|
||||
('Capital Remote Armor Repair Systems', 'armorDamageAmount', True),
|
||||
('Capital Shield Emission Systems', 'shieldBonus', True),
|
||||
@@ -28944,14 +28871,15 @@ class Effect6581(BaseEffect):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill(skill), 'falloffEffectiveness',
|
||||
src.getModifiedItemAttr('siegeRemoteLogisticsRangeBonus'), stackingPenalties=True)
|
||||
|
||||
# Local armor/shield rep effects (duration / amoutn)
|
||||
# Local armor/shield rep effects (duration / amount)
|
||||
for skill, amtAttr in (
|
||||
('Capital Shield Operation', 'shieldBonus'),
|
||||
('Capital Repair Systems', 'armorDamageAmount')):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill(skill), 'duration',
|
||||
src.getModifiedItemAttr('siegeLocalLogisticsDurationBonus'))
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill(skill), amtAttr,
|
||||
src.getModifiedItemAttr('siegeLocalLogisticsAmountBonus'))
|
||||
src.getModifiedItemAttr('siegeLocalLogisticsAmountBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Speed bonus
|
||||
fit.ship.boostItemAttr('maxVelocity', src.getModifiedItemAttr('speedFactor'), stackingPenalties=True)
|
||||
@@ -29016,6 +28944,10 @@ class Effect6582(BaseEffect):
|
||||
mod.item.requiresSkill('Capital Projectile Turret'),
|
||||
'damageMultiplier', src.getModifiedItemAttr('siegeTurretDamageBonus'))
|
||||
|
||||
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill('Motion Prediction'),
|
||||
'damageMultiplier', src.getModifiedItemAttr('siegeHAWTurretDamageBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Missiles
|
||||
for type in ('kinetic', 'thermal', 'explosive', 'em'):
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('XL Torpedoes') or
|
||||
@@ -29023,7 +28955,18 @@ class Effect6582(BaseEffect):
|
||||
mod.charge.requiresSkill('Torpedoes'),
|
||||
'%sDamage' % type, src.getModifiedItemAttr('siegeMissileDamageBonus'))
|
||||
|
||||
# Reppers
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('XL Torpedoes') or
|
||||
mod.item.requiresSkill('XL Cruise Missiles'),
|
||||
'speed', src.getModifiedItemAttr('siegeLauncherROFBonus'))
|
||||
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Target Navigation Prediction'),
|
||||
'speed', src.getModifiedItemAttr('siegeHAWMissileROFBonus'))
|
||||
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Torpedoes'),
|
||||
'maxVelocity', src.getModifiedItemAttr('siegeTorpedoVelocityBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Tank
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Shield Operation') or
|
||||
mod.item.requiresSkill('Capital Repair Systems'),
|
||||
'duration', src.getModifiedItemAttr('siegeLocalLogisticsDurationBonus'))
|
||||
@@ -29036,36 +28979,20 @@ class Effect6582(BaseEffect):
|
||||
'armorDamageAmount', src.getModifiedItemAttr('siegeLocalLogisticsAmountBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
# Speed penalty
|
||||
# Mobility & safety penalties
|
||||
fit.ship.boostItemAttr('maxVelocity', src.getModifiedItemAttr('speedFactor'))
|
||||
|
||||
# Mass
|
||||
fit.ship.multiplyItemAttr('mass', src.getModifiedItemAttr('siegeMassMultiplier'),
|
||||
stackingPenalties=True, penaltyGroup='postMul')
|
||||
|
||||
# @ todo: test for April 2016 release
|
||||
# Block Hostile EWAR and friendly effects
|
||||
fit.ship.forceItemAttr('disallowOffensiveModifiers', src.getModifiedItemAttr('disallowOffensiveModifiers'))
|
||||
fit.ship.forceItemAttr('disallowAssistance', src.getModifiedItemAttr('disallowAssistance'))
|
||||
|
||||
# new in April 2016 release
|
||||
# missile ROF bonus
|
||||
for group in ('Missile Launcher XL Torpedo', 'Missile Launcher Rapid Torpedo', 'Missile Launcher XL Cruise'):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == group, 'speed',
|
||||
src.getModifiedItemAttr('siegeLauncherROFBonus'))
|
||||
|
||||
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill('Torpedoes'), 'maxVelocity',
|
||||
src.getModifiedItemAttr('siegeTorpedoVelocityBonus'), stackingPenalties=True)
|
||||
|
||||
fit.ship.increaseItemAttr('warpScrambleStatus', src.getModifiedItemAttr('siegeModeWarpStatus'))
|
||||
fit.ship.forceItemAttr('disallowDocking', src.getModifiedItemAttr('disallowDocking'))
|
||||
fit.ship.forceItemAttr('disallowTethering', src.getModifiedItemAttr('disallowTethering'))
|
||||
|
||||
# Ewar and assistance resistances
|
||||
fit.ship.boostItemAttr('remoteRepairImpedance', src.getModifiedItemAttr('remoteRepairImpedanceBonus'))
|
||||
fit.ship.boostItemAttr('sensorDampenerResistance', src.getModifiedItemAttr('sensorDampenerResistanceBonus'))
|
||||
fit.ship.boostItemAttr('remoteAssistanceImpedance', src.getModifiedItemAttr('remoteAssistanceImpedanceBonus'))
|
||||
fit.ship.boostItemAttr('weaponDisruptionResistance', src.getModifiedItemAttr('weaponDisruptionResistanceBonus'))
|
||||
|
||||
fit.ship.forceItemAttr('disallowDocking', src.getModifiedItemAttr('disallowDocking'))
|
||||
fit.ship.forceItemAttr('disallowTethering', src.getModifiedItemAttr('disallowTethering'))
|
||||
|
||||
|
||||
class Effect6591(BaseEffect):
|
||||
"""
|
||||
@@ -30542,7 +30469,14 @@ class Effect6672(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context):
|
||||
secModifier = module.getModifiedItemAttr('securityModifier')
|
||||
secMap = {
|
||||
FitSystemSecurity.HISEC: 'hiSecModifier',
|
||||
FitSystemSecurity.LOWSEC: 'lowSecModifier',
|
||||
FitSystemSecurity.NULLSEC: 'nullSecModifier',
|
||||
FitSystemSecurity.WSPACE: 'nullSecModifier'}
|
||||
fitSec = fit.getSystemSecurity()
|
||||
attrName = secMap[fitSec]
|
||||
secModifier = module.getModifiedItemAttr(attrName)
|
||||
module.multiplyItemAttr('structureRigDoomsdayDamageLossTargetBonus', secModifier)
|
||||
module.multiplyItemAttr('structureRigScanResBonus', secModifier)
|
||||
module.multiplyItemAttr('structureRigPDRangeBonus', secModifier)
|
||||
@@ -32278,7 +32212,7 @@ class Effect6845(BaseEffect):
|
||||
shipBonusCommandDestroyerRole1DefenderBonus
|
||||
|
||||
Used by:
|
||||
Ships from group: Command Destroyer (4 of 4)
|
||||
Ships from group: Command Destroyer (4 of 5)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -32537,7 +32471,7 @@ class Effect6871(BaseEffect):
|
||||
# Get pilot sec status bonus directly here, instead of going through the intermediary effects
|
||||
# via https://forums.eveonline.com/default.aspx?g=posts&t=515826
|
||||
try:
|
||||
bonus = max(0, min(50.0, (src.parent.character.secStatus * 10)))
|
||||
bonus = max(0, min(50.0, (src.owner.character.secStatus * 10)))
|
||||
except:
|
||||
bonus = None
|
||||
|
||||
@@ -33827,10 +33761,10 @@ class Effect6994(BaseEffect):
|
||||
|
||||
class Effect6995(BaseEffect):
|
||||
"""
|
||||
targetABCAttack
|
||||
targetDisintegratorAttack
|
||||
|
||||
Used by:
|
||||
Modules from group: Precursor Weapon (15 of 15)
|
||||
Modules from group: Precursor Weapon (18 of 18)
|
||||
"""
|
||||
|
||||
type = 'active'
|
||||
@@ -33994,6 +33928,7 @@ class Effect7012(BaseEffect):
|
||||
|
||||
Used by:
|
||||
Variations of module: Assault Damage Control I (5 of 5)
|
||||
Module: Abyssal Assault Damage Control
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -34170,6 +34105,7 @@ class Effect7026(BaseEffect):
|
||||
@staticmethod
|
||||
def handler(fit, src, context, *args, **kwargs):
|
||||
src.boostItemAttr('maxRange', src.getModifiedChargeAttr('warpScrambleRangeBonus'))
|
||||
src.forceItemAttr('activationBlockedStrenght', src.getModifiedChargeAttr('activationBlockedStrenght'))
|
||||
|
||||
|
||||
class Effect7027(BaseEffect):
|
||||
@@ -34877,7 +34813,7 @@ class Effect7077(BaseEffect):
|
||||
disintegratorWeaponDamageMultiply
|
||||
|
||||
Used by:
|
||||
Modules from group: Entropic Radiation Sink (4 of 4)
|
||||
Modules from group: Entropic Radiation Sink (6 of 6)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -34894,7 +34830,7 @@ class Effect7078(BaseEffect):
|
||||
disintegratorWeaponSpeedMultiply
|
||||
|
||||
Used by:
|
||||
Modules from group: Entropic Radiation Sink (4 of 4)
|
||||
Modules from group: Entropic Radiation Sink (6 of 6)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -34943,8 +34879,8 @@ class Effect7085(BaseEffect):
|
||||
shipbonusPCTDamagePC1
|
||||
|
||||
Used by:
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -34960,8 +34896,8 @@ class Effect7086(BaseEffect):
|
||||
shipbonusPCTTrackingPC2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -34977,7 +34913,6 @@ class Effect7087(BaseEffect):
|
||||
shipbonusPCTOptimalPF2
|
||||
|
||||
Used by:
|
||||
Ship: Damavik
|
||||
Ship: Hydra
|
||||
"""
|
||||
|
||||
@@ -34994,7 +34929,7 @@ class Effect7088(BaseEffect):
|
||||
shipbonusPCTDamagePF1
|
||||
|
||||
Used by:
|
||||
Ship: Damavik
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
Ship: Hydra
|
||||
"""
|
||||
|
||||
@@ -35026,13 +34961,13 @@ class Effect7092(BaseEffect):
|
||||
shipBonusRemoteRepCapNeedRoleBonus2
|
||||
|
||||
Used by:
|
||||
Ship: Damavik
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Drekavac
|
||||
Ship: Hydra
|
||||
Ship: Kikimora
|
||||
Ship: Leshak
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35048,14 +34983,14 @@ class Effect7093(BaseEffect):
|
||||
shipBonusSmartbombCapNeedRoleBonus2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
Variations of ship: Rodiva (2 of 2)
|
||||
Ship: Damavik
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Drekavac
|
||||
Ship: Hydra
|
||||
Ship: Kikimora
|
||||
Ship: Leshak
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35071,13 +35006,13 @@ class Effect7094(BaseEffect):
|
||||
shipBonusRemoteRepMaxRangeRoleBonus1
|
||||
|
||||
Used by:
|
||||
Ship: Damavik
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Drekavac
|
||||
Ship: Hydra
|
||||
Ship: Kikimora
|
||||
Ship: Leshak
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35109,7 +35044,7 @@ class Effect7111(BaseEffect):
|
||||
systemSmallPrecursorTurretDamage
|
||||
|
||||
Used by:
|
||||
Celestials named like: Wolf Rayet Effect Beacon Class (5 of 6)
|
||||
Celestials named like: Wolf Rayet Effect Beacon Class (6 of 6)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
@@ -35127,13 +35062,13 @@ class Effect7112(BaseEffect):
|
||||
shipBonusNeutCapNeedRoleBonus2
|
||||
|
||||
Used by:
|
||||
Ship: Damavik
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
Variations of ship: Vedmak (2 of 2)
|
||||
Ship: Drekavac
|
||||
Ship: Hydra
|
||||
Ship: Kikimora
|
||||
Ship: Leshak
|
||||
Ship: Tiamat
|
||||
Ship: Vedmak
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35239,7 +35174,7 @@ class Effect7154(BaseEffect):
|
||||
shipBonusPD1DisintegratorDamage
|
||||
|
||||
Used by:
|
||||
Ship: Kikimora
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35289,7 +35224,7 @@ class Effect7157(BaseEffect):
|
||||
shipBonusPD2DisintegratorMaxRange
|
||||
|
||||
Used by:
|
||||
Ship: Kikimora
|
||||
Variations of ship: Kikimora (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
@@ -35595,5 +35530,283 @@ class Effect7183(BaseEffect):
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Warp Scrambler', 'maxRange',
|
||||
src.getModifiedItemAttr('warpScrambleRangeBonus'), stackingPenalties=False)
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == 'Warp Scrambler',
|
||||
'maxRange', src.getModifiedItemAttr('warpScrambleRangeBonus'),
|
||||
stackingPenalties=False)
|
||||
|
||||
|
||||
class Effect7184(BaseEffect):
|
||||
"""
|
||||
shipBonusMediumDroneHProle8
|
||||
|
||||
Used by:
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Medium Drone Operation'),
|
||||
'hp', ship.getModifiedItemAttr('shipBonusRole8'))
|
||||
|
||||
|
||||
class Effect7185(BaseEffect):
|
||||
"""
|
||||
shipBonusMediumDroneShieldHProle8
|
||||
|
||||
Used by:
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Medium Drone Operation'),
|
||||
'shieldCapacity', ship.getModifiedItemAttr('shipBonusRole8'))
|
||||
|
||||
|
||||
class Effect7186(BaseEffect):
|
||||
"""
|
||||
shipBonusMediumDroneArmorHProle8
|
||||
|
||||
Used by:
|
||||
Ship: Gila
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Medium Drone Operation'),
|
||||
'armorHP', ship.getModifiedItemAttr('shipBonusRole8'))
|
||||
|
||||
|
||||
class Effect7193(BaseEffect):
|
||||
"""
|
||||
systemMiningCycleTimeBonus
|
||||
|
||||
Used by:
|
||||
Celestials named like: Invasion Effects (3 of 3)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = ('projected', 'passive')
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, beacon, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Mining'),
|
||||
'duration', beacon.getModifiedItemAttr('miningDurationMultiplier'))
|
||||
|
||||
|
||||
class Effect7202(BaseEffect):
|
||||
"""
|
||||
systemDroneSpeedBonusPercent
|
||||
|
||||
Used by:
|
||||
Celestials named like: Invasion Effects (3 of 3)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = ('projected', 'passive')
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, beacon, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'maxVelocity', beacon.getModifiedItemAttr('droneMaxVelocityBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
|
||||
class Effect7203(BaseEffect):
|
||||
"""
|
||||
systemDroneDamageBonusPercent
|
||||
|
||||
Used by:
|
||||
Celestials named like: Invasion Effects (3 of 3)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = ('projected', 'passive')
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, beacon, context):
|
||||
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill('Drones'),
|
||||
'damageMultiplier', beacon.getModifiedItemAttr('droneDamageBonus'),
|
||||
stackingPenalties=True)
|
||||
|
||||
|
||||
class Effect7204(BaseEffect):
|
||||
"""
|
||||
shipArmorEMResistancePF2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr('armorEmDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
|
||||
|
||||
|
||||
class Effect7205(BaseEffect):
|
||||
"""
|
||||
shipArmorKinResistancePF2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr('armorKineticDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
|
||||
|
||||
|
||||
class Effect7206(BaseEffect):
|
||||
"""
|
||||
shipArmorThermResistancePF2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr('armorThermalDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
|
||||
|
||||
|
||||
class Effect7207(BaseEffect):
|
||||
"""
|
||||
shipArmorExpResistancePF2
|
||||
|
||||
Used by:
|
||||
Variations of ship: Damavik (2 of 2)
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.ship.boostItemAttr('armorExplosiveDamageResonance', ship.getModifiedItemAttr('shipBonusPF2'), skill='Precursor Frigate')
|
||||
|
||||
|
||||
class Effect7209(BaseEffect):
|
||||
"""
|
||||
shipPCTOptimalBonusEliteGunship2
|
||||
|
||||
Used by:
|
||||
Ship: Nergal
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Precursor Weapon'),
|
||||
'maxRange', ship.getModifiedItemAttr('eliteBonusGunship2'),
|
||||
skill='Assault Frigates')
|
||||
|
||||
|
||||
class Effect7210(BaseEffect):
|
||||
"""
|
||||
shipBonusCommandDestroyerRole2DefenderBonus
|
||||
|
||||
Used by:
|
||||
Ship: Draugur
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Defender Missiles'),
|
||||
'moduleReactivationDelay', ship.getModifiedItemAttr('shipBonusRole2'))
|
||||
|
||||
|
||||
class Effect7211(BaseEffect):
|
||||
"""
|
||||
shipDmgMultiMaxEliteHeavyGunship1
|
||||
|
||||
Used by:
|
||||
Ship: Ikitursa
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Precursor Weapon'),
|
||||
'damageMultiplierBonusMax', ship.getModifiedItemAttr('eliteBonusHeavyGunship1'),
|
||||
skill='Heavy Assault Cruisers')
|
||||
|
||||
|
||||
class Effect7216(BaseEffect):
|
||||
"""
|
||||
shipDmgMultiMaxEliteGunship1
|
||||
|
||||
Used by:
|
||||
Ship: Nergal
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Precursor Weapon'),
|
||||
'damageMultiplierBonusMax', ship.getModifiedItemAttr('eliteBonusGunship1'),
|
||||
skill='Assault Frigates')
|
||||
|
||||
|
||||
class Effect7223(BaseEffect):
|
||||
"""
|
||||
systemAgilityBonusPercentItem
|
||||
|
||||
Used by:
|
||||
Celestials named like: Invasion Effects (3 of 3)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = ('projected', 'passive')
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, beacon, context):
|
||||
fit.ship.boostItemAttr('agility', beacon.getModifiedItemAttr('agilityBonus'), stackingPenalties=True)
|
||||
|
||||
|
||||
class Effect7227(BaseEffect):
|
||||
"""
|
||||
systemHullHPBonusPercentItem
|
||||
|
||||
Used by:
|
||||
Celestials named like: Invasion Effects (3 of 3)
|
||||
"""
|
||||
|
||||
runTime = 'early'
|
||||
type = ('projected', 'passive')
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, beacon, context):
|
||||
fit.ship.boostItemAttr('hp', beacon.getModifiedItemAttr('hullHpBonus'))
|
||||
|
||||
|
||||
class Effect7228(BaseEffect):
|
||||
"""
|
||||
shipMediumPrecursorWeaponOptimalEliteHeavyGunship2
|
||||
|
||||
Used by:
|
||||
Ship: Ikitursa
|
||||
"""
|
||||
|
||||
type = 'passive'
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Medium Precursor Weapon'),
|
||||
'maxRange', ship.getModifiedItemAttr('eliteBonusHeavyGunship2'),
|
||||
skill='Heavy Assault Cruisers')
|
||||
|
||||
@@ -140,12 +140,18 @@ class Effect(EqBase):
|
||||
Whether this effect is implemented in code or not,
|
||||
unimplemented effects simply do nothing at all when run
|
||||
"""
|
||||
if not self.__generated:
|
||||
self.__generateHandler()
|
||||
|
||||
return self.__effectDef is not None
|
||||
|
||||
def isType(self, type):
|
||||
"""
|
||||
Check if this effect is of the passed type
|
||||
"""
|
||||
if not self.__generated:
|
||||
self.__generateHandler()
|
||||
|
||||
return self.type is not None and type in self.type
|
||||
|
||||
def __generateHandler(self):
|
||||
@@ -244,6 +250,17 @@ class Item(EqBase):
|
||||
|
||||
return self.__attributes
|
||||
|
||||
@property
|
||||
def attribsWithOverrides(self):
|
||||
overrides = self.overrides
|
||||
attribs = {}
|
||||
for aname, attr in self.attributes.items():
|
||||
if aname in overrides:
|
||||
attribs[aname] = overrides[aname]
|
||||
else:
|
||||
attribs[aname] = attr
|
||||
return attribs
|
||||
|
||||
def getAttribute(self, key, default=None):
|
||||
if key in self.attributes:
|
||||
return self.attributes[key].value
|
||||
@@ -471,9 +488,33 @@ class Item(EqBase):
|
||||
def getAbyssalTypes(cls):
|
||||
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
||||
|
||||
@property
|
||||
def isModule(self):
|
||||
return self.category.name in ('Module', 'Structure Module')
|
||||
|
||||
@property
|
||||
def isSubsystem(self):
|
||||
return self.category.name == 'Subsystem'
|
||||
|
||||
@property
|
||||
def isCharge(self):
|
||||
return self.category.name == "Charge"
|
||||
return self.category.name == 'Charge'
|
||||
|
||||
@property
|
||||
def isDrone(self):
|
||||
return self.category.name == 'Drone'
|
||||
|
||||
@property
|
||||
def isFighter(self):
|
||||
return self.category.name == 'Fighter'
|
||||
|
||||
@property
|
||||
def isImplant(self):
|
||||
return self.category.name == 'Implant' and self.group.name != 'Booster'
|
||||
|
||||
@property
|
||||
def isBooster(self):
|
||||
return self.group.name == 'Booster' and self.category.name == 'Implant'
|
||||
|
||||
def __repr__(self):
|
||||
return "Item(ID={}, name={}) at {}".format(
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
import itertools
|
||||
|
||||
|
||||
class Graph(object):
|
||||
class Graph:
|
||||
|
||||
def __init__(self, fit, function, data=None):
|
||||
self.fit = fit
|
||||
self.data = {}
|
||||
@@ -50,11 +51,11 @@ class Graph(object):
|
||||
point = {}
|
||||
for i in range(len(pointValues)):
|
||||
point[pointNames[i]] = pointValues[i]
|
||||
|
||||
yield point, self.function(point)
|
||||
|
||||
|
||||
class Data(object):
|
||||
class Data:
|
||||
|
||||
def __init__(self, name, dataString, step=None):
|
||||
self.name = name
|
||||
self.step = step
|
||||
@@ -83,7 +84,8 @@ class Data(object):
|
||||
return len(self.data) == 1 and self.data[0].isConstant()
|
||||
|
||||
|
||||
class Constant(object):
|
||||
class Constant:
|
||||
|
||||
def __init__(self, const):
|
||||
if isinstance(const, str):
|
||||
self.value = None if const == "" else float(const)
|
||||
@@ -98,7 +100,8 @@ class Constant(object):
|
||||
return True
|
||||
|
||||
|
||||
class Range(object):
|
||||
class Range:
|
||||
|
||||
def __init__(self, string, step):
|
||||
start, end = string.split("-")
|
||||
self.start = float(start)
|
||||
@@ -108,8 +111,8 @@ class Range(object):
|
||||
def __iter__(self):
|
||||
current = start = self.start
|
||||
end = self.end
|
||||
step = self.step or (end - start) / 50.0
|
||||
i = 1
|
||||
step = self.step or (end - start) / 200
|
||||
i = 0
|
||||
while current < end:
|
||||
current = start + i * step
|
||||
i += 1
|
||||
|
||||
24
eos/graph/fitCapAmountTime.py
Normal file
24
eos/graph/fitCapAmountTime.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitCapAmountTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcAmount, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcAmount(self, data):
|
||||
time = data["time"]
|
||||
maxCap = self.fit.ship.getModifiedItemAttr('capacitorCapacity')
|
||||
regenTime = self.fit.ship.getModifiedItemAttr('rechargeRate') / 1000
|
||||
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
|
||||
cap = maxCap * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
|
||||
return cap
|
||||
25
eos/graph/fitCapRegenAmount.py
Normal file
25
eos/graph/fitCapRegenAmount.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitCapRegenAmountGraph(Graph):
|
||||
|
||||
defaults = {"percentage": '0-100'}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcRegen, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcRegen(self, data):
|
||||
perc = data['percentage']
|
||||
maxCap = self.fit.ship.getModifiedItemAttr('capacitorCapacity')
|
||||
regenTime = self.fit.ship.getModifiedItemAttr('rechargeRate') / 1000
|
||||
currentCap = maxCap * perc / 100
|
||||
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
|
||||
regen = 10 * maxCap / regenTime * (math.sqrt(currentCap / maxCap) - currentCap / maxCap)
|
||||
return regen
|
||||
26
eos/graph/fitDistanceTime.py
Normal file
26
eos/graph/fitDistanceTime.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitDistanceTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcDistance, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcDistance(self, data):
|
||||
time = data["time"]
|
||||
maxSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
|
||||
mass = self.fit.ship.getModifiedItemAttr('mass')
|
||||
agility = self.fit.ship.getModifiedItemAttr('agility')
|
||||
# Definite integral of:
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
distance = maxSpeed * time + (maxSpeed * agility * mass * math.exp((-time * 1000000) / (agility * mass)) / 1000000)
|
||||
return distance
|
||||
113
eos/graph/fitDmgTime.py
Normal file
113
eos/graph/fitDmgTime.py
Normal file
@@ -0,0 +1,113 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitDmgTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcDmg, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
self.__cache = {}
|
||||
|
||||
def calcDmg(self, data):
|
||||
time = data["time"] * 1000
|
||||
closestTime = max((t for t in self.__cache if t <= time), default=None)
|
||||
if closestTime is None:
|
||||
return 0
|
||||
return self.__cache[closestTime]
|
||||
|
||||
def recalc(self):
|
||||
|
||||
def addDmg(addedTime, addedDmg):
|
||||
if addedDmg == 0:
|
||||
return
|
||||
if addedTime not in self.__cache:
|
||||
prevTime = max((t for t in self.__cache if t < addedTime), default=None)
|
||||
if prevTime is None:
|
||||
self.__cache[addedTime] = 0
|
||||
else:
|
||||
self.__cache[addedTime] = self.__cache[prevTime]
|
||||
for time in (t for t in self.__cache if t >= addedTime):
|
||||
self.__cache[time] += addedDmg
|
||||
|
||||
self.__cache.clear()
|
||||
fit = self.fit
|
||||
# We'll handle calculations in milliseconds
|
||||
maxTime = self.data["time"].data[0].end * 1000
|
||||
for mod in fit.modules:
|
||||
cycleParams = mod.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
nonstopCycles = 0
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
addDmg(currentTime + volleyTime, volley.total)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if inactiveTime == 0:
|
||||
nonstopCycles += 1
|
||||
else:
|
||||
nonstopCycles = 0
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for drone in fit.drones:
|
||||
cycleParams = drone.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
volleyParams = drone.getVolleyParameters()
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
addDmg(currentTime + volleyTime, volley.total)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for fighter in fit.fighters:
|
||||
cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
volleyParams = fighter.getVolleyParametersPerEffect()
|
||||
for effectID, abilityCycleParams in cycleParams.items():
|
||||
if effectID not in volleyParams:
|
||||
continue
|
||||
currentTime = 0
|
||||
abilityVolleyParams = volleyParams[effectID]
|
||||
for cycleTime, inactiveTime in abilityCycleParams.iterCycles():
|
||||
for volleyTime, volley in abilityVolleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
addDmg(currentTime + volleyTime, volley.total)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
@@ -17,16 +17,19 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from math import log, sin, radians, exp
|
||||
from math import exp, log, radians, sin, inf
|
||||
|
||||
from eos.graph import Graph
|
||||
from eos.const import FittingModuleState, FittingHardpoint
|
||||
from logbook import Logger
|
||||
|
||||
from eos.const import FittingHardpoint, FittingModuleState
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitDpsGraph(Graph):
|
||||
class FitDpsRangeGraph(Graph):
|
||||
|
||||
defaults = {
|
||||
"angle" : 0,
|
||||
"distance" : 0,
|
||||
@@ -95,10 +98,13 @@ class FitDpsGraph(Graph):
|
||||
for fighter in fit.fighters:
|
||||
if not fighter.active:
|
||||
continue
|
||||
fighterDpsMap = fighter.getDpsPerEffect(targetResists=fit.targetResists)
|
||||
for ability in fighter.abilities:
|
||||
if ability.dealsDamage and ability.active:
|
||||
if ability.effectID not in fighterDpsMap:
|
||||
continue
|
||||
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
||||
dps = ability.getDps(targetResists=fit.targetResists).total
|
||||
dps = fighterDpsMap[ability.effectID].total
|
||||
total += dps * multiplier
|
||||
|
||||
return total
|
||||
@@ -166,8 +172,7 @@ class FitDpsGraph(Graph):
|
||||
|
||||
return min(sigRadiusFactor, velocityFactor, 1)
|
||||
|
||||
@staticmethod
|
||||
def calculateTurretChanceToHit(mod, data):
|
||||
def calculateTurretChanceToHit(self, mod, data):
|
||||
distance = data["distance"] * 1000
|
||||
tracking = mod.getModifiedItemAttr("trackingSpeed")
|
||||
turretOptimal = mod.maxRange
|
||||
@@ -176,7 +181,17 @@ class FitDpsGraph(Graph):
|
||||
targetSigRad = data["signatureRadius"]
|
||||
targetSigRad = turretSigRes if targetSigRad is None else targetSigRad
|
||||
transversal = sin(radians(data["angle"])) * data["velocity"]
|
||||
trackingEq = (((transversal / (distance * tracking)) *
|
||||
|
||||
# Angular velocity is calculated using range from ship center to target center.
|
||||
# We do not know target radius but we know attacker radius
|
||||
angDistance = distance + self.fit.ship.getModifiedItemAttr('radius', 0)
|
||||
if angDistance == 0 and transversal == 0:
|
||||
angularVelocity = 0
|
||||
elif angDistance == 0 and transversal != 0:
|
||||
angularVelocity = inf
|
||||
else:
|
||||
angularVelocity = transversal / angDistance
|
||||
trackingEq = (((angularVelocity / tracking) *
|
||||
(turretSigRes / targetSigRad)) ** 2)
|
||||
rangeEq = ((max(0, distance - turretOptimal)) / turretFalloff) ** 2
|
||||
|
||||
112
eos/graph/fitDpsTime.py
Normal file
112
eos/graph/fitDpsTime.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitDpsTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcDps, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
self.__cache = []
|
||||
|
||||
def calcDps(self, data):
|
||||
time = data["time"] * 1000
|
||||
entries = (e for e in self.__cache if e[0] <= time < e[1])
|
||||
dps = sum(e[2] for e in entries)
|
||||
return dps
|
||||
|
||||
def recalc(self):
|
||||
|
||||
def addDmg(addedTimeStart, addedTimeFinish, addedDmg):
|
||||
if addedDmg == 0:
|
||||
return
|
||||
addedDps = 1000 * addedDmg / (addedTimeFinish - addedTimeStart)
|
||||
self.__cache.append((addedTimeStart, addedTimeFinish, addedDps))
|
||||
|
||||
self.__cache = []
|
||||
fit = self.fit
|
||||
# We'll handle calculations in milliseconds
|
||||
maxTime = self.data["time"].data[0].end * 1000
|
||||
for mod in fit.modules:
|
||||
cycleParams = mod.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
nonstopCycles = 0
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
cycleDamage = 0
|
||||
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
cycleDamage += volley.total
|
||||
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if inactiveTime > 0:
|
||||
nonstopCycles = 0
|
||||
else:
|
||||
nonstopCycles += 1
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for drone in fit.drones:
|
||||
cycleParams = drone.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
cycleDamage = 0
|
||||
volleyParams = drone.getVolleyParameters()
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
cycleDamage += volley.total
|
||||
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for fighter in fit.fighters:
|
||||
cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
volleyParams = fighter.getVolleyParametersPerEffect()
|
||||
for effectID, abilityCycleParams in cycleParams.items():
|
||||
if effectID not in volleyParams:
|
||||
continue
|
||||
abilityVolleyParams = volleyParams[effectID]
|
||||
currentTime = 0
|
||||
for cycleTime, inactiveTime in abilityCycleParams.iterCycles():
|
||||
cycleDamage = 0
|
||||
for volleyTime, volley in abilityVolleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime and volleyTime <= cycleTime:
|
||||
cycleDamage += volley.total
|
||||
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
29
eos/graph/fitShieldAmountTime.py
Normal file
29
eos/graph/fitShieldAmountTime.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitShieldAmountTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcAmount, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
import gui.mainFrame
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def calcAmount(self, data):
|
||||
time = data["time"]
|
||||
maxShield = self.fit.ship.getModifiedItemAttr('shieldCapacity')
|
||||
regenTime = self.fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
|
||||
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
|
||||
shield = maxShield * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
if self.fit.damagePattern is not None and useEhp:
|
||||
shield = self.fit.damagePattern.effectivify(self.fit, shield, 'shield')
|
||||
return shield
|
||||
49
eos/graph/fitShieldRegenAmount.py
Normal file
49
eos/graph/fitShieldRegenAmount.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitShieldRegenAmountGraph(Graph):
|
||||
|
||||
defaults = {"percentage": '0-100'}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcRegen, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
import gui.mainFrame
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def calcRegen(self, data):
|
||||
perc = data["percentage"]
|
||||
maxShield = self.fit.ship.getModifiedItemAttr('shieldCapacity')
|
||||
regenTime = self.fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
|
||||
currentShield = maxShield * perc / 100
|
||||
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
|
||||
regen = 10 * maxShield / regenTime * (math.sqrt(currentShield / maxShield) - currentShield / maxShield)
|
||||
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
|
||||
if self.fit.damagePattern is not None and useEhp:
|
||||
regen = self.fit.damagePattern.effectivify(self.fit, regen, 'shield')
|
||||
return regen
|
||||
25
eos/graph/fitSpeedTime.py
Normal file
25
eos/graph/fitSpeedTime.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FitSpeedTimeGraph(Graph):
|
||||
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcSpeed, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcSpeed(self, data):
|
||||
time = data["time"]
|
||||
maxSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
|
||||
mass = self.fit.ship.getModifiedItemAttr('mass')
|
||||
agility = self.fit.ship.getModifiedItemAttr('agility')
|
||||
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
|
||||
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
|
||||
return speed
|
||||
61
eos/graph/fitWarpTimeDistance.py
Normal file
61
eos/graph/fitWarpTimeDistance.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from eos.graph import Graph
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
AU_METERS = 149597870700
|
||||
|
||||
|
||||
class FitWarpTimeDistanceGraph(Graph):
|
||||
|
||||
defaults = {"distance": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcTime, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcTime(self, data):
|
||||
distance = data["distance"]
|
||||
if distance == 0:
|
||||
return 0
|
||||
maxWarpDistance = self.fit.maxWarpDistance
|
||||
if distance > maxWarpDistance:
|
||||
return None
|
||||
maxSubwarpSpeed = self.fit.ship.getModifiedItemAttr('maxVelocity')
|
||||
maxWarpSpeed = self.fit.warpSpeed
|
||||
time = calculate_time_in_warp(maxWarpSpeed, maxSubwarpSpeed, distance * AU_METERS)
|
||||
return time
|
||||
|
||||
|
||||
# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation
|
||||
# with minor modifications
|
||||
# Warp speed in AU/s, subwarp speed in m/s, distance in m
|
||||
def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist):
|
||||
|
||||
k_accel = max_warp_speed
|
||||
k_decel = min(max_warp_speed / 3, 2)
|
||||
|
||||
warp_dropout_speed = max_subwarp_speed / 2
|
||||
max_ms_warp_speed = max_warp_speed * AU_METERS
|
||||
|
||||
accel_dist = AU_METERS
|
||||
decel_dist = max_ms_warp_speed / k_decel
|
||||
|
||||
minimum_dist = accel_dist + decel_dist
|
||||
|
||||
cruise_time = 0
|
||||
|
||||
if minimum_dist > warp_dist:
|
||||
max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel)
|
||||
else:
|
||||
cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed
|
||||
|
||||
accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel
|
||||
decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel
|
||||
|
||||
total_time = cruise_time + accel_time + decel_time
|
||||
return total_time
|
||||
@@ -33,13 +33,20 @@ class ItemAttrShortcut(object):
|
||||
|
||||
return return_value or default
|
||||
|
||||
def getBaseAttrValue(self, key, default=0):
|
||||
def getItemBaseAttrValue(self, key, default=0):
|
||||
"""
|
||||
Gets base value in this order:
|
||||
Mutated value > override value > attribute value
|
||||
"""
|
||||
return_value = self.itemModifiedAttributes.getOriginal(key)
|
||||
return return_value or default
|
||||
|
||||
def getChargeBaseAttrValue(self, key, default=0):
|
||||
"""
|
||||
Gets base value in this order:
|
||||
Mutated value > override value > attribute value
|
||||
"""
|
||||
return_value = self.chargeModifiedAttributes.getOriginal(key)
|
||||
return return_value or default
|
||||
|
||||
|
||||
@@ -58,8 +65,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
pass
|
||||
|
||||
def __init__(self, fit=None, parent=None):
|
||||
self.__fit = fit
|
||||
self.parent = parent
|
||||
self.fit = fit
|
||||
# Stores original values of the entity
|
||||
self.__original = None
|
||||
# Modified values during calculations
|
||||
@@ -94,6 +101,22 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
self.__penalizedMultipliers.clear()
|
||||
self.__postIncreases.clear()
|
||||
|
||||
@property
|
||||
def fit(self):
|
||||
# self.fit is usually set during fit calculations when the item is registered with the fit. However,
|
||||
# under certain circumstances, an effect will not work as it will try to modify an item which has NOT
|
||||
# yet been registered and thus has not had self.fit set. In this case, use the modules owner attribute
|
||||
# to point to the correct fit. See GH Issue #434
|
||||
if self.__fit is not None:
|
||||
return self.__fit
|
||||
if hasattr(self.parent, 'owner'):
|
||||
return self.parent.owner
|
||||
return None
|
||||
|
||||
@fit.setter
|
||||
def fit(self, fit):
|
||||
self.__fit = fit
|
||||
|
||||
@property
|
||||
def original(self):
|
||||
return self.__original
|
||||
@@ -195,13 +218,13 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if attrInfo is None:
|
||||
cappingId = cappingAttrKeyCache[key] = None
|
||||
else:
|
||||
# see GH issue #620
|
||||
cappingId = cappingAttrKeyCache[key] = attrInfo.maxAttributeID
|
||||
cappingId = attrInfo.maxAttributeID
|
||||
if cappingId is None:
|
||||
cappingKey = None
|
||||
else:
|
||||
cappingAttrInfo = getAttributeInfo(cappingId)
|
||||
cappingKey = None if cappingAttrInfo is None else cappingAttrInfo.name
|
||||
cappingAttrKeyCache[key] = cappingKey
|
||||
|
||||
if cappingKey:
|
||||
cappingValue = self.original.get(cappingKey, self.__calculateValue(cappingKey))
|
||||
@@ -215,7 +238,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if force is not None:
|
||||
if cappingValue is not None:
|
||||
force = min(force, cappingValue)
|
||||
if key in (50, 30, 48, 11):
|
||||
if key in ("cpu", "power", "cpuOutput", "powerOutput"):
|
||||
force = round(force, 2)
|
||||
return force
|
||||
# Grab our values if they're there, otherwise we'll take default values
|
||||
@@ -236,11 +259,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
dv = attrInfo.defaultValue
|
||||
default = defaultValuesCache[key] = dv if dv is not None else 0.0
|
||||
|
||||
val = self.__intermediary.get(key,
|
||||
self.__preAssigns.get(key,
|
||||
self.getOriginal(key, default)
|
||||
)
|
||||
)
|
||||
val = self.__intermediary.get(key, self.__preAssigns.get(key, self.getOriginal(key, default)))
|
||||
|
||||
# We'll do stuff in the following order:
|
||||
# preIncrease > multiplier > stacking penalized multipliers > postIncrease
|
||||
@@ -270,7 +289,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
# Cap value if we have cap defined
|
||||
if cappingValue is not None:
|
||||
val = min(val, cappingValue)
|
||||
if key in (50, 30, 48, 11):
|
||||
if key in ("cpu", "power", "cpuOutput", "powerOutput"):
|
||||
val = round(val, 2)
|
||||
return val
|
||||
|
||||
@@ -282,14 +301,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
with the fit and thus get the correct affector. Returns skill level to
|
||||
be used to modify modifier. See GH issue #101
|
||||
"""
|
||||
fit = self.fit
|
||||
if not fit:
|
||||
# self.fit is usually set during fit calculations when the item is registered with the fit. However,
|
||||
# under certain circumstances, an effect will not work as it will try to modify an item which has NOT
|
||||
# yet been registered and thus has not had self.fit set. In this case, use the modules owner attribute
|
||||
# to point to the correct fit. See GH Issue #434
|
||||
fit = self.parent.owner
|
||||
skill = fit.character.getSkill(skillName)
|
||||
skill = self.fit.character.getSkill(skillName)
|
||||
self.__tmpModifier = skill
|
||||
return skill.level
|
||||
|
||||
@@ -302,14 +314,15 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
def __afflict(self, attributeName, operation, bonus, used=True):
|
||||
"""Add modifier to list of things affecting current item"""
|
||||
# Do nothing if no fit is assigned
|
||||
if self.fit is None:
|
||||
fit = self.fit
|
||||
if fit is None:
|
||||
return
|
||||
# Create dictionary for given attribute and give it alias
|
||||
if attributeName not in self.__affectedBy:
|
||||
self.__affectedBy[attributeName] = {}
|
||||
affs = self.__affectedBy[attributeName]
|
||||
origin = self.fit.getOrigin()
|
||||
fit = origin if origin and origin != self.fit else self.fit
|
||||
origin = fit.getOrigin()
|
||||
fit = origin if origin and origin != fit else fit
|
||||
# If there's no set for current fit in dictionary, create it
|
||||
if fit not in affs:
|
||||
affs[fit] = []
|
||||
@@ -321,7 +334,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
modifier = self.__tmpModifier
|
||||
self.__tmpModifier = None
|
||||
else:
|
||||
modifier = self.fit.getModifier()
|
||||
modifier = fit.getModifier()
|
||||
|
||||
# Add current affliction to list
|
||||
affs.append((modifier, operation, bonus, used))
|
||||
|
||||
@@ -27,7 +27,7 @@ from sqlalchemy.orm import validates, reconstructor
|
||||
import eos
|
||||
import eos.db
|
||||
import eos.config
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledImplantBoosterList
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledImplantList
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -51,7 +51,7 @@ class Character(object):
|
||||
for item in self.getSkillList():
|
||||
self.addSkill(Skill(self, item.ID, self.defaultLevel))
|
||||
|
||||
self.__implants = HandledImplantBoosterList()
|
||||
self.__implants = HandledImplantList()
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.utils.cycles import CycleInfo
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
@@ -104,7 +105,16 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def cycleTime(self):
|
||||
return max(self.getModifiedItemAttr("duration", 0), 0)
|
||||
if self.hasAmmo:
|
||||
cycleTime = self.getModifiedItemAttr("missileLaunchDuration", 0)
|
||||
else:
|
||||
for attr in ("speed", "duration"):
|
||||
cycleTime = self.getModifiedItemAttr(attr, None)
|
||||
if cycleTime is not None:
|
||||
break
|
||||
if cycleTime is None:
|
||||
return 0
|
||||
return max(cycleTime, 0)
|
||||
|
||||
@property
|
||||
def dealsDamage(self):
|
||||
@@ -121,9 +131,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
def getVolley(self, targetResists=None):
|
||||
def getVolleyParameters(self, targetResists=None):
|
||||
if not self.dealsDamage or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
|
||||
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
|
||||
@@ -137,15 +147,19 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
return {0: volley}
|
||||
|
||||
def getVolley(self, targetResists=None):
|
||||
return self.getVolleyParameters(targetResists=targetResists)[0]
|
||||
|
||||
def getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed"
|
||||
cycleTime = self.getModifiedItemAttr(cycleAttr)
|
||||
dpsFactor = 1 / (cycleTime / 1000)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
dpsFactor = 1 / (cycleParams.averageTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
@@ -153,6 +167,12 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def getCycleParameters(self, reloadOverride=None):
|
||||
cycleTime = self.cycleTime
|
||||
if cycleTime == 0:
|
||||
return None
|
||||
return CycleInfo(self.cycleTime, 0, math.inf)
|
||||
|
||||
def getRemoteReps(self, ignoreState=False):
|
||||
if self.amountActive <= 0 and not ignoreState:
|
||||
return (None, 0)
|
||||
@@ -174,24 +194,27 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
rrAmount = 0
|
||||
if rrAmount:
|
||||
droneAmount = self.amount if ignoreState else self.amountActive
|
||||
rrAmount *= droneAmount / (self.cycleTime / 1000)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
rrType = None
|
||||
rrAmount = 0
|
||||
else:
|
||||
rrAmount *= droneAmount / (cycleParams.averageTime / 1000)
|
||||
self.__baseRemoteReps = (rrType, rrAmount)
|
||||
return self.__baseRemoteReps
|
||||
|
||||
def changeType(self, typeID):
|
||||
self.itemID = typeID
|
||||
self.init()
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
if self.mines is True and self.amountActive > 0:
|
||||
attr = "duration"
|
||||
getter = self.getModifiedItemAttr
|
||||
|
||||
cycleTime = self.getModifiedItemAttr(attr)
|
||||
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
cycleTime = cycleParams.averageTime
|
||||
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
else:
|
||||
self.__miningyield = 0
|
||||
|
||||
|
||||
@@ -17,16 +17,19 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import math
|
||||
from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.utils.stats import DmgTypes
|
||||
from eos.const import FittingSlot
|
||||
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.float import floatUnerr
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -172,88 +175,125 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
def getVolley(self, targetResists=None):
|
||||
def getVolleyParametersPerEffect(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return {}
|
||||
if self.__baseVolley is None:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
self.__baseVolley = {}
|
||||
for ability in self.abilities:
|
||||
# Not passing resists here as we want to calculate and store base volley
|
||||
abilityVolley = ability.getVolley()
|
||||
em += abilityVolley.em
|
||||
therm += abilityVolley.thermal
|
||||
kin += abilityVolley.kinetic
|
||||
exp += abilityVolley.explosive
|
||||
self.__baseVolley = DmgTypes(em, therm, kin, exp)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
self.__baseVolley[ability.effectID] = {0: ability.getVolley()}
|
||||
adjustedVolley = {}
|
||||
for effectID, effectData in self.__baseVolley.items():
|
||||
adjustedVolley[effectID] = {}
|
||||
for volleyTime, volleyValue in effectData.items():
|
||||
adjustedVolley[effectID][volleyTime] = DmgTypes(
|
||||
em=volleyValue.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=volleyValue.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=volleyValue.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=volleyValue.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return adjustedVolley
|
||||
|
||||
def getVolley(self, targetResists=None):
|
||||
volleyParams = self.getVolleyParametersPerEffect(targetResists=targetResists)
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for volleyData in volleyParams.values():
|
||||
em += volleyData[0].em
|
||||
therm += volleyData[0].thermal
|
||||
kin += volleyData[0].kinetic
|
||||
exp += volleyData[0].explosive
|
||||
return DmgTypes(em, therm, kin, exp)
|
||||
|
||||
def getDps(self, targetResists=None):
|
||||
em = 0
|
||||
thermal = 0
|
||||
kinetic = 0
|
||||
explosive = 0
|
||||
for dps in self.getDpsPerEffect(targetResists=targetResists).values():
|
||||
em += dps.em
|
||||
thermal += dps.thermal
|
||||
kinetic += dps.kinetic
|
||||
explosive += dps.explosive
|
||||
return DmgTypes(em=em, thermal=thermal, kinetic=kinetic, explosive=explosive)
|
||||
|
||||
def getDpsPerEffect(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Analyze cooldowns when reload is factored in
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
peakEm = 0
|
||||
peakTherm = 0
|
||||
peakKin = 0
|
||||
peakExp = 0
|
||||
steadyEm = 0
|
||||
steadyTherm = 0
|
||||
steadyKin = 0
|
||||
steadyExp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
# Peak dps
|
||||
peakEm += abilityDps.em
|
||||
peakTherm += abilityDps.thermal
|
||||
peakKin += abilityDps.kinetic
|
||||
peakExp += abilityDps.explosive
|
||||
# Infinite use - add to steady dps
|
||||
if ability.numShots == 0:
|
||||
steadyEm += abilityDps.em
|
||||
steadyTherm += abilityDps.thermal
|
||||
steadyKin += abilityDps.kinetic
|
||||
steadyExp += abilityDps.explosive
|
||||
else:
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
steadyDps = DmgTypes(steadyEm, steadyTherm, steadyKin, steadyExp)
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
peakDps = DmgTypes(peakEm, peakTherm, peakKin, peakExp)
|
||||
peakAdjustFactor = shortestActive / (shortestActive + longestReload)
|
||||
peakDpsAdjusted = DmgTypes(
|
||||
em=peakDps.em * peakAdjustFactor,
|
||||
thermal=peakDps.thermal * peakAdjustFactor,
|
||||
kinetic=peakDps.kinetic * peakAdjustFactor,
|
||||
explosive=peakDps.explosive * peakAdjustFactor)
|
||||
dps = max(steadyDps, peakDpsAdjusted, key=lambda d: d.total)
|
||||
return dps
|
||||
return {}
|
||||
cycleParams = self.getCycleParametersPerEffectOptimizedDps(targetResists=targetResists)
|
||||
dpsMap = {}
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in cycleParams:
|
||||
cycleTime = cycleParams[ability.effectID].averageTime
|
||||
dpsMap[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
|
||||
return dpsMap
|
||||
|
||||
def getCycleParametersPerEffectOptimizedDps(self, targetResists=None, reloadOverride=None):
|
||||
cycleParamsInfinite = self.getCycleParametersPerEffectInfinite()
|
||||
cycleParamsReload = self.getCycleParametersPerEffect(reloadOverride=reloadOverride)
|
||||
dpsMapOnlyInfinite = {}
|
||||
dpsMapAllWithReloads = {}
|
||||
# Decide if it's better to keep steady dps up and never reload or reload from time to time
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in cycleParamsInfinite:
|
||||
cycleTime = cycleParamsInfinite[ability.effectID].averageTime
|
||||
dpsMapOnlyInfinite[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
|
||||
if ability.effectID in cycleParamsReload:
|
||||
cycleTime = cycleParamsReload[ability.effectID].averageTime
|
||||
dpsMapAllWithReloads[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
|
||||
totalOnlyInfinite = sum(i.total for i in dpsMapOnlyInfinite.values())
|
||||
totalAllWithReloads = sum(i.total for i in dpsMapAllWithReloads.values())
|
||||
return cycleParamsInfinite if totalOnlyInfinite >= totalAllWithReloads else cycleParamsReload
|
||||
|
||||
def getCycleParametersPerEffectInfinite(self):
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.numShots == 0 and a.cycleTime > 0}
|
||||
|
||||
def getCycleParametersPerEffect(self, reloadOverride=None):
|
||||
factorReload = reloadOverride if reloadOverride is not None else self.owner.factorReload
|
||||
# Assume it can cycle infinitely
|
||||
if not factorReload:
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
|
||||
limitedAbilities = [a for a in self.abilities if a.numShots > 0 and a.cycleTime > 0]
|
||||
if len(limitedAbilities) == 0:
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
|
||||
validAbilities = [a for a in self.abilities if a.cycleTime > 0]
|
||||
if len(validAbilities) == 0:
|
||||
return {}
|
||||
mostLimitedAbility = min(limitedAbilities, key=lambda a: a.cycleTime * a.numShots)
|
||||
durationToRefuel = mostLimitedAbility.cycleTime * mostLimitedAbility.numShots
|
||||
# find out how many shots various abilities will do until reload, and how much time
|
||||
# "extra" cycle will last (None for no extra cycle)
|
||||
cyclesUntilRefuel = {mostLimitedAbility.effectID: (mostLimitedAbility.numShots, None)}
|
||||
for ability in (a for a in validAbilities if a is not mostLimitedAbility):
|
||||
fullCycles = int(floatUnerr(durationToRefuel / ability.cycleTime))
|
||||
extraShotTime = floatUnerr(durationToRefuel - (fullCycles * ability.cycleTime))
|
||||
if extraShotTime == 0:
|
||||
extraShotTime = None
|
||||
cyclesUntilRefuel[ability.effectID] = (fullCycles, extraShotTime)
|
||||
refuelTimes = {}
|
||||
for ability in validAbilities:
|
||||
spentShots, extraShotTime = cyclesUntilRefuel[ability.effectID]
|
||||
if extraShotTime is not None:
|
||||
spentShots += 1
|
||||
refuelTimes[ability.effectID] = ability.getReloadTime(spentShots)
|
||||
refuelTime = max(refuelTimes.values())
|
||||
cycleParams = {}
|
||||
for ability in validAbilities:
|
||||
regularShots, extraShotTime = cyclesUntilRefuel[ability.effectID]
|
||||
sequence = []
|
||||
if extraShotTime is not None:
|
||||
if regularShots > 0:
|
||||
sequence.append(CycleInfo(ability.cycleTime, 0, regularShots))
|
||||
sequence.append(CycleInfo(extraShotTime, refuelTime, 1))
|
||||
else:
|
||||
return steadyDps
|
||||
# Just sum all abilities when not taking reload into consideration
|
||||
else:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
em += abilityDps.em
|
||||
therm += abilityDps.thermal
|
||||
kin += abilityDps.kinetic
|
||||
exp += abilityDps.explosive
|
||||
return DmgTypes(em, therm, kin, exp)
|
||||
regularShotsNonReload = regularShots - 1
|
||||
if regularShotsNonReload > 0:
|
||||
sequence.append(CycleInfo(ability.cycleTime, 0, regularShotsNonReload))
|
||||
sequence.append(CycleInfo(ability.cycleTime, refuelTime, 1))
|
||||
cycleParams[ability.effectID] = CycleSequence(sequence, math.inf)
|
||||
return cycleParams
|
||||
|
||||
@property
|
||||
def maxRange(self):
|
||||
|
||||
@@ -95,8 +95,15 @@ class FighterAbility(object):
|
||||
|
||||
@property
|
||||
def reloadTime(self):
|
||||
return self.getReloadTime()
|
||||
|
||||
def getReloadTime(self, spentShots=None):
|
||||
if spentShots is not None:
|
||||
spentShots = max(self.numShots, spentShots)
|
||||
else:
|
||||
spentShots = self.numShots
|
||||
rearm_time = (self.REARM_TIME_MAPPING[self.fighter.getModifiedItemAttr("fighterSquadronRole")] or 0 if self.hasCharges else 0)
|
||||
return self.fighter.getModifiedItemAttr("fighterRefuelingTime") + rearm_time * self.numShots
|
||||
return self.fighter.getModifiedItemAttr("fighterRefuelingTime") + rearm_time * spentShots
|
||||
|
||||
@property
|
||||
def numShots(self):
|
||||
@@ -105,17 +112,6 @@ class FighterAbility(object):
|
||||
@property
|
||||
def cycleTime(self):
|
||||
speed = self.fighter.getModifiedItemAttr("{}Duration".format(self.attrPrefix))
|
||||
|
||||
# Factor in reload
|
||||
'''
|
||||
reload = self.reloadTime
|
||||
|
||||
if self.fighter.owner.factorReload:
|
||||
numShots = self.numShots
|
||||
# Speed here already takes into consideration reactivation time
|
||||
speed = (speed * numShots + reload) / numShots if numShots > 0 else speed
|
||||
'''
|
||||
|
||||
return speed
|
||||
|
||||
def getVolley(self, targetResists=None):
|
||||
@@ -139,11 +135,12 @@ class FighterAbility(object):
|
||||
explosive=exp * dmgMult * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def getDps(self, targetResists=None):
|
||||
def getDps(self, targetResists=None, cycleTimeOverride=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
dpsFactor = 1 / (self.cycleTime / 1000)
|
||||
cycleTime = cycleTimeOverride if cycleTimeOverride is not None else self.cycleTime
|
||||
dpsFactor = 1 / (cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
|
||||
@@ -17,26 +17,27 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import datetime
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from math import sqrt, log, asinh
|
||||
import datetime
|
||||
|
||||
from sqlalchemy.orm import validates, reconstructor
|
||||
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.effectHandlerHelpers import HandledModuleList, HandledDroneCargoList, HandledImplantBoosterList, HandledProjectedDroneList, HandledProjectedModList
|
||||
from eos.const import ImplantLocation, CalcType, FittingSlot
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
|
||||
from eos.effectHandlerHelpers import (
|
||||
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
|
||||
HandledModuleList, HandledProjectedDroneList, HandledProjectedModList)
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.const import FittingModuleState, FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from eos.saveddata.ship import Ship
|
||||
from eos.utils.stats import DmgTypes
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -48,17 +49,19 @@ class Fit(object):
|
||||
|
||||
def __init__(self, ship=None, name=""):
|
||||
"""Initialize a fit from the program"""
|
||||
self.__ship = None
|
||||
self.__mode = None
|
||||
# use @mode.setter's to set __attr and IDs. This will set mode as well
|
||||
self.ship = ship
|
||||
if self.ship:
|
||||
self.ship.parent = self
|
||||
self.ship.owner = self
|
||||
|
||||
self.__modules = HandledModuleList()
|
||||
self.__drones = HandledDroneCargoList()
|
||||
self.__fighters = HandledDroneCargoList()
|
||||
self.__cargo = HandledDroneCargoList()
|
||||
self.__implants = HandledImplantBoosterList()
|
||||
self.__boosters = HandledImplantBoosterList()
|
||||
self.__implants = HandledImplantList()
|
||||
self.__boosters = HandledBoosterList()
|
||||
# self.__projectedFits = {}
|
||||
self.__projectedModules = HandledProjectedModList()
|
||||
self.__projectedDrones = HandledProjectedDroneList()
|
||||
@@ -103,9 +106,9 @@ class Fit(object):
|
||||
if self.modeID and self.__ship:
|
||||
item = eos.db.getItem(self.modeID)
|
||||
# Don't need to verify if it's a proper item, as validateModeItem assures this
|
||||
self.__mode = self.ship.validateModeItem(item)
|
||||
self.__mode = self.ship.validateModeItem(item, owner=self)
|
||||
else:
|
||||
self.__mode = self.ship.validateModeItem(None)
|
||||
self.__mode = self.ship.validateModeItem(None, owner=self)
|
||||
|
||||
self.build()
|
||||
|
||||
@@ -166,8 +169,12 @@ class Fit(object):
|
||||
|
||||
@mode.setter
|
||||
def mode(self, mode):
|
||||
if self.__mode is not None:
|
||||
self.__mode.owner = None
|
||||
self.__mode = mode
|
||||
self.modeID = mode.item.ID if mode is not None else None
|
||||
if mode is not None:
|
||||
mode.owner = self
|
||||
|
||||
@property
|
||||
def modifiedCoalesce(self):
|
||||
@@ -200,11 +207,14 @@ class Fit(object):
|
||||
|
||||
@ship.setter
|
||||
def ship(self, ship):
|
||||
if self.__ship is not None:
|
||||
self.__ship.owner = None
|
||||
self.__ship = ship
|
||||
self.shipID = ship.item.ID if ship is not None else None
|
||||
if ship is not None:
|
||||
ship.owner = self
|
||||
# set mode of new ship
|
||||
self.mode = self.ship.validateModeItem(None) if ship is not None else None
|
||||
self.mode = self.ship.validateModeItem(None, owner=self) if ship is not None else None
|
||||
# set fit attributes the same as ship
|
||||
self.extraAttributes = self.ship.itemModifiedAttributes
|
||||
|
||||
@@ -474,7 +484,7 @@ class Fit(object):
|
||||
if hasattr(currModifier.itemModifiedAttributes, "fit"):
|
||||
currModifier.itemModifiedAttributes.fit = origin or self
|
||||
if hasattr(currModifier, "chargeModifiedAttributes"):
|
||||
if hasattr(currModifier.itemModifiedAttributes, "fit"):
|
||||
if hasattr(currModifier.chargeModifiedAttributes, "fit"):
|
||||
currModifier.chargeModifiedAttributes.fit = origin or self
|
||||
|
||||
def getModifier(self):
|
||||
@@ -500,7 +510,6 @@ class Fit(object):
|
||||
continue
|
||||
|
||||
# This should always be a gang effect, otherwise it wouldn't be added to commandBonuses
|
||||
# @todo: Check this
|
||||
if effect.isType("gang"):
|
||||
self.register(thing)
|
||||
|
||||
@@ -916,8 +925,10 @@ class Fit(object):
|
||||
recalc. Figure out a way to keep track of any changes to slot layout and call this automatically
|
||||
"""
|
||||
if self.ship is None:
|
||||
return
|
||||
return {}
|
||||
|
||||
# Look for any dummies of that type to remove
|
||||
posToRemove = {}
|
||||
for slotType in (FittingSlot.LOW.value, FittingSlot.MED.value, FittingSlot.HIGH.value, FittingSlot.RIG.value, FittingSlot.SUBSYSTEM.value, FittingSlot.SERVICE.value):
|
||||
amount = self.getSlotsFree(slotType, True)
|
||||
if amount > 0:
|
||||
@@ -925,16 +936,17 @@ class Fit(object):
|
||||
self.modules.append(Module.buildEmpty(slotType))
|
||||
|
||||
if amount < 0:
|
||||
# Look for any dummies of that type to remove
|
||||
toRemove = []
|
||||
for mod in self.modules:
|
||||
if mod.isEmpty and mod.slot == slotType:
|
||||
toRemove.append(mod)
|
||||
pos = self.modules.index(mod)
|
||||
posToRemove[pos] = slotType
|
||||
amount += 1
|
||||
if amount == 0:
|
||||
break
|
||||
for mod in toRemove:
|
||||
self.modules.remove(mod)
|
||||
for pos in sorted(posToRemove, reverse=True):
|
||||
mod = self.modules[pos]
|
||||
self.modules.remove(mod)
|
||||
return posToRemove
|
||||
|
||||
def unfill(self):
|
||||
for i in range(len(self.modules) - 1, -1, -1):
|
||||
@@ -1069,9 +1081,12 @@ class Fit(object):
|
||||
for f in self.fighters:
|
||||
if f.active:
|
||||
amount += 1
|
||||
|
||||
return amount
|
||||
|
||||
@property
|
||||
def fighterTubesTotal(self):
|
||||
return self.ship.getModifiedItemAttr("fighterTubes")
|
||||
|
||||
@property
|
||||
def cargoBayUsed(self):
|
||||
amount = 0
|
||||
@@ -1496,20 +1511,45 @@ class Fit(object):
|
||||
|
||||
return True
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
copy_ship = Fit()
|
||||
# Character and owner are not copied
|
||||
copy_ship.character = self.__character
|
||||
copy_ship.owner = self.owner
|
||||
copy_ship.ship = deepcopy(self.ship)
|
||||
copy_ship.name = "%s copy" % self.name
|
||||
copy_ship.damagePattern = self.damagePattern
|
||||
copy_ship.targetResists = self.targetResists
|
||||
copy_ship.implantLocation = self.implantLocation
|
||||
copy_ship.notes = self.notes
|
||||
def getReleaseLimitForDrone(self, item):
|
||||
if not item.isDrone:
|
||||
return 0
|
||||
bw = round(self.ship.getModifiedItemAttr("droneBandwidth"))
|
||||
volume = round(item.attribsWithOverrides['volume'].value)
|
||||
return int(bw / volume)
|
||||
|
||||
def getStoreLimitForDrone(self, item):
|
||||
if not item.isDrone:
|
||||
return 0
|
||||
bayTotal = round(self.ship.getModifiedItemAttr("droneCapacity"))
|
||||
bayUsed = round(self.droneBayUsed)
|
||||
volume = item.attribsWithOverrides['volume'].value
|
||||
return int((bayTotal - bayUsed) / volume)
|
||||
|
||||
def getSystemSecurity(self):
|
||||
secstatus = self.systemSecurity
|
||||
# Default to nullsec
|
||||
if secstatus is None:
|
||||
secstatus = FitSystemSecurity.NULLSEC
|
||||
return secstatus
|
||||
|
||||
def __deepcopy__(self, memo=None):
|
||||
fitCopy = Fit()
|
||||
# Character and owner are not copied
|
||||
fitCopy.character = self.__character
|
||||
fitCopy.owner = self.owner
|
||||
fitCopy.ship = deepcopy(self.ship)
|
||||
fitCopy.mode = deepcopy(self.mode)
|
||||
fitCopy.name = "%s copy" % self.name
|
||||
fitCopy.damagePattern = self.damagePattern
|
||||
fitCopy.targetResists = self.targetResists
|
||||
fitCopy.implantLocation = self.implantLocation
|
||||
fitCopy.systemSecurity = self.systemSecurity
|
||||
fitCopy.notes = self.notes
|
||||
|
||||
for i in self.modules:
|
||||
fitCopy.modules.appendIgnoreEmpty(deepcopy(i))
|
||||
toCopy = (
|
||||
"modules",
|
||||
"drones",
|
||||
"fighters",
|
||||
"cargo",
|
||||
@@ -1520,7 +1560,7 @@ class Fit(object):
|
||||
"projectedFighters")
|
||||
for name in toCopy:
|
||||
orig = getattr(self, name)
|
||||
c = getattr(copy_ship, name)
|
||||
c = getattr(fitCopy, name)
|
||||
for i in orig:
|
||||
c.append(deepcopy(i))
|
||||
|
||||
@@ -1530,22 +1570,22 @@ class Fit(object):
|
||||
eos.db.saveddata_session.refresh(fit)
|
||||
|
||||
for fit in self.commandFits:
|
||||
copy_ship.commandFitDict[fit.ID] = fit
|
||||
fitCopy.commandFitDict[fit.ID] = fit
|
||||
forceUpdateSavedata(fit)
|
||||
copyCommandInfo = fit.getCommandInfo(copy_ship.ID)
|
||||
copyCommandInfo = fit.getCommandInfo(fitCopy.ID)
|
||||
originalCommandInfo = fit.getCommandInfo(self.ID)
|
||||
copyCommandInfo.active = originalCommandInfo.active
|
||||
forceUpdateSavedata(fit)
|
||||
|
||||
for fit in self.projectedFits:
|
||||
copy_ship.projectedFitDict[fit.ID] = fit
|
||||
fitCopy.projectedFitDict[fit.ID] = fit
|
||||
forceUpdateSavedata(fit)
|
||||
copyProjectionInfo = fit.getProjectionInfo(copy_ship.ID)
|
||||
copyProjectionInfo = fit.getProjectionInfo(fitCopy.ID)
|
||||
originalProjectionInfo = fit.getProjectionInfo(self.ID)
|
||||
copyProjectionInfo.active = originalProjectionInfo.active
|
||||
forceUpdateSavedata(fit)
|
||||
|
||||
return copy_ship
|
||||
return fitCopy
|
||||
|
||||
def __repr__(self):
|
||||
return "Fit(ID={}, ship={}, name={}) at {}".format(
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from eos.effectHandlerHelpers import HandledImplantBoosterList
|
||||
from eos.effectHandlerHelpers import HandledImplantList
|
||||
|
||||
|
||||
class ImplantSet(object):
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
self.__implants = HandledImplantBoosterList()
|
||||
self.__implants = HandledImplantList()
|
||||
|
||||
@property
|
||||
def implants(self):
|
||||
|
||||
@@ -22,17 +22,19 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut
|
||||
|
||||
|
||||
class Mode(ItemAttrShortcut, HandledItem):
|
||||
def __init__(self, item):
|
||||
|
||||
|
||||
def __init__(self, item, owner=None):
|
||||
if item.group.name != "Ship Modifiers":
|
||||
raise ValueError(
|
||||
'Passed item "%s" (category: (%s)) is not a Ship Modifier' % (item.name, item.category.name))
|
||||
|
||||
self.owner = owner
|
||||
self.__item = item
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.item.attributes
|
||||
self.__itemModifiedAttributes.overrides = self.item.overrides
|
||||
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
return self.__item
|
||||
@@ -53,3 +55,12 @@ class Mode(ItemAttrShortcut, HandledItem):
|
||||
for effect in self.item.effects.values():
|
||||
if effect.runTime == runTime and effect.activeByDefault:
|
||||
effect.handler(fit, self, context=("module",))
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
copy = Mode(self.item)
|
||||
return copy
|
||||
|
||||
def __repr__(self):
|
||||
return "Mode(ID={}, name={}) at {}".format(
|
||||
self.item.ID, self.item.name, hex(id(self))
|
||||
)
|
||||
|
||||
@@ -17,21 +17,22 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from math import floor
|
||||
|
||||
from logbook import Logger
|
||||
import math
|
||||
from sqlalchemy.orm import reconstructor, validates
|
||||
|
||||
import eos.db
|
||||
from eos.const import FittingModuleState, FittingHardpoint, FittingSlot
|
||||
from eos.const import FittingHardpoint, FittingModuleState, FittingSlot
|
||||
from eos.effectHandlerHelpers import HandledCharge, HandledItem
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.utils.cycles import CycleInfo, CycleSequence
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
ProjectedMap = {
|
||||
@@ -192,21 +193,25 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# todo: validate baseItem as well if it's set.
|
||||
if self.isEmpty:
|
||||
return False
|
||||
return self.__item is None or \
|
||||
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
||||
self.__item.group.name not in self.SYSTEM_GROUPS) or \
|
||||
(self.item.isAbyssal and (not self.baseItemID or not self.mutaplasmidID))
|
||||
return (
|
||||
self.__item is None or (
|
||||
self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
|
||||
self.__item.group.name not in self.SYSTEM_GROUPS) or
|
||||
(self.item.isAbyssal and not self.isMutated))
|
||||
|
||||
@property
|
||||
def isMutated(self):
|
||||
return self.baseItemID or self.mutaplasmidID
|
||||
return self.baseItemID and self.mutaplasmidID
|
||||
|
||||
@property
|
||||
def numCharges(self):
|
||||
if self.charge is None:
|
||||
return self.getNumCharges(self.charge)
|
||||
|
||||
def getNumCharges(self, charge):
|
||||
if charge is None:
|
||||
charges = 0
|
||||
else:
|
||||
chargeVolume = self.charge.volume
|
||||
chargeVolume = charge.volume
|
||||
containerCapacity = self.item.capacity
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
@@ -235,8 +240,19 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@property
|
||||
def modPosition(self):
|
||||
if self.owner:
|
||||
return self.owner.modules.index(self) if not self.isProjected else self.owner.projectedModules.index(self)
|
||||
return self.getModPosition()
|
||||
|
||||
def getModPosition(self, fit=None):
|
||||
# Pass in fit for reliability. When it's not passed, we rely on owner and owner
|
||||
# is set by sqlalchemy during flush
|
||||
fit = fit if fit is not None else self.owner
|
||||
if fit:
|
||||
container = fit.projectedModules if self.isProjected else fit.modules
|
||||
try:
|
||||
return container.index(self)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@property
|
||||
def isProjected(self):
|
||||
@@ -272,7 +288,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
|
||||
chargeRate = self.getModifiedItemAttr("chargeRate")
|
||||
numCharges = self.numCharges
|
||||
numShots = floor(numCharges / chargeRate)
|
||||
numShots = math.floor(numCharges / chargeRate)
|
||||
else:
|
||||
numShots = None
|
||||
return numShots
|
||||
@@ -285,7 +301,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
chance = self.getModifiedChargeAttr("crystalVolatilityChance")
|
||||
damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
|
||||
crystals = self.numCharges
|
||||
numShots = floor((crystals * hp) / (damage * chance))
|
||||
numShots = math.floor((crystals * hp) / (damage * chance))
|
||||
else:
|
||||
# Set 0 (infinite) for permanent crystals like t1 laser crystals
|
||||
numShots = 0
|
||||
@@ -385,8 +401,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
|
||||
"miningAmount") or 0
|
||||
if volley:
|
||||
cycleTime = self.cycleTime
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
cycleTime = cycleParams.averageTime
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
else:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
@@ -394,42 +414,64 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
return self.__miningyield
|
||||
|
||||
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
def getVolleyParameters(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
if self.__baseVolley is None:
|
||||
self.__baseVolley = {}
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||
self.__baseVolley = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0) or self.getModifiedItemAttr("doomsdayWarningDuration", 0)
|
||||
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
|
||||
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
|
||||
if dmgDuration != 0 and dmgSubcycle != 0:
|
||||
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
|
||||
else:
|
||||
subcycles = 1
|
||||
for i in range(subcycles):
|
||||
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
spoolBoost = calculateSpoolup(
|
||||
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
|
||||
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||
spoolMultiplier = 1 + spoolBoost
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
adjustedVolley = {}
|
||||
for volleyTime, volleyValue in self.__baseVolley.items():
|
||||
adjustedVolley[volleyTime] = DmgTypes(
|
||||
em=volleyValue.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return adjustedVolley
|
||||
|
||||
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
if len(volleyParams) == 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
return volleyParams[min(volleyParams)]
|
||||
|
||||
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Some weapons repeat multiple times in one cycle (bosonic doomsdays). Get the number of times it fires off
|
||||
volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1)
|
||||
dpsFactor = volleysPerCycle / (self.cycleTime / 1000)
|
||||
dmgDuringCycle = DmgTypes(0, 0, 0, 0)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return dmgDuringCycle
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
avgCycleTime = cycleParams.averageTime
|
||||
if len(volleyParams) == 0 or avgCycleTime == 0:
|
||||
return dmgDuringCycle
|
||||
for volleyValue in volleyParams.values():
|
||||
dmgDuringCycle += volleyValue
|
||||
dpsFactor = 1 / (avgCycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
em=dmgDuringCycle.em * dpsFactor,
|
||||
thermal=dmgDuringCycle.thermal * dpsFactor,
|
||||
kinetic=dmgDuringCycle.kinetic * dpsFactor,
|
||||
explosive=dmgDuringCycle.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
|
||||
@@ -459,7 +501,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return None, 0
|
||||
if rrAmount:
|
||||
rrAmount *= 1 / (self.cycleTime / 1000)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return None, 0
|
||||
rrAmount *= 1 / (cycleParams.averageTime / 1000)
|
||||
if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
|
||||
rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
||||
|
||||
@@ -535,6 +580,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if not fits and fit.ignoreRestrictions:
|
||||
self.restrictionOverridden = True
|
||||
fits = True
|
||||
elif fits and fit.ignoreRestrictions:
|
||||
self.restrictionOverridden = False
|
||||
|
||||
return fits
|
||||
|
||||
@@ -552,6 +599,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.slot == FittingSlot.SUBSYSTEM:
|
||||
subSlot = self.getModifiedItemAttr("subSystemSlot")
|
||||
for mod in fit.modules:
|
||||
if mod is self:
|
||||
continue
|
||||
if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
|
||||
return False
|
||||
|
||||
@@ -566,7 +615,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
current = 0 # if self.owner != fit else -1 # Disabled, see #1278
|
||||
for mod in fit.modules:
|
||||
if (mod.item and mod.item.groupID == self.item.groupID and
|
||||
self.modPosition != mod.modPosition):
|
||||
self.getModPosition(fit) != mod.getModPosition(fit)):
|
||||
current += 1
|
||||
|
||||
if current >= max:
|
||||
@@ -586,13 +635,19 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
# Check if we're within bounds
|
||||
if state < -1 or state > 2:
|
||||
return False
|
||||
elif state >= FittingModuleState.ACTIVE and not self.item.isType("active"):
|
||||
elif state >= FittingModuleState.ACTIVE and (not self.item.isType("active") or self.getModifiedItemAttr('activationBlocked') > 0):
|
||||
return False
|
||||
elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def getMaxState(self, proposedState=None):
|
||||
states = sorted((s for s in FittingModuleState if proposedState is None or s <= proposedState), reverse=True)
|
||||
for state in states:
|
||||
if self.isValidState(state):
|
||||
return state
|
||||
|
||||
def canHaveState(self, state=None, projectedOnto=None):
|
||||
"""
|
||||
Check with other modules if there are restrictions that might not allow this module to be activated
|
||||
@@ -750,28 +805,26 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
context = ("module",)
|
||||
projected = False
|
||||
|
||||
# if gang:
|
||||
# context += ("commandRun",)
|
||||
|
||||
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:
|
||||
for effect in self.charge.effects.values():
|
||||
if effect.runTime == runTime and \
|
||||
effect.activeByDefault and \
|
||||
(effect.isType("offline") or
|
||||
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) and \
|
||||
(not gang or (gang and effect.isType("gang"))):
|
||||
|
||||
chargeContext = ("moduleCharge",)
|
||||
# For gang effects, we pass in the effect itself as an argument. However, to avoid going through
|
||||
# all the effect files and defining this argument, do a simple try/catch here and be done with it.
|
||||
if (
|
||||
effect.runTime == runTime and
|
||||
effect.activeByDefault and (
|
||||
effect.isType("offline") or
|
||||
(effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) and
|
||||
(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, chargeContext, effect=effect)
|
||||
effect.handler(fit, self, contexts, effect=effect)
|
||||
except:
|
||||
effect.handler(fit, self, chargeContext)
|
||||
effect.handler(fit, self, contexts)
|
||||
|
||||
if self.item:
|
||||
if self.state >= FittingModuleState.OVERHEATED:
|
||||
@@ -796,49 +849,61 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
except:
|
||||
effect.handler(fit, self, context)
|
||||
|
||||
@property
|
||||
def cycleTime(self):
|
||||
def getCycleParameters(self, reloadOverride=None):
|
||||
"""Copied from new eos as well"""
|
||||
# Determine if we'll take into account reload time or not
|
||||
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
|
||||
|
||||
numShots = self.numShots
|
||||
speed = self.rawCycleTime
|
||||
|
||||
if factorReload and self.charge:
|
||||
raw_reload_time = self.reloadTime
|
||||
if reloadOverride is not None:
|
||||
factorReload = reloadOverride
|
||||
else:
|
||||
raw_reload_time = 0.0
|
||||
factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
|
||||
|
||||
# Module can only fire one shot at a time, think bomb launchers or defender launchers
|
||||
if self.disallowRepeatingAction:
|
||||
if numShots > 0:
|
||||
"""
|
||||
The actual mechanics behind this is complex. Behavior will be (for 3 ammo):
|
||||
fire, reactivation delay, fire, reactivation delay, fire, max(reactivation delay, reload)
|
||||
so your effective reload time depends on where you are at in the cycle.
|
||||
cycles_until_reload = self.numShots
|
||||
if cycles_until_reload == 0:
|
||||
cycles_until_reload = math.inf
|
||||
|
||||
We can't do that, so instead we'll average it out.
|
||||
|
||||
Currently would apply to bomb launchers and defender missiles
|
||||
"""
|
||||
effective_reload_time = ((self.reactivationDelay * (numShots - 1)) + max(raw_reload_time, self.reactivationDelay, 0))
|
||||
else:
|
||||
"""
|
||||
Applies to MJD/MJFG
|
||||
"""
|
||||
effective_reload_time = max(raw_reload_time, self.reactivationDelay, 0)
|
||||
speed = speed + effective_reload_time
|
||||
active_time = self.rawCycleTime
|
||||
if active_time == 0:
|
||||
return None
|
||||
forced_inactive_time = self.reactivationDelay
|
||||
reload_time = self.reloadTime
|
||||
# Effects which cannot be reloaded have the same processing whether
|
||||
# caller wants to take reload time into account or not
|
||||
if reload_time is None and cycles_until_reload < math.inf:
|
||||
final_cycles = 1
|
||||
early_cycles = cycles_until_reload - final_cycles
|
||||
# Single cycle until effect cannot run anymore
|
||||
if early_cycles == 0:
|
||||
return CycleInfo(active_time, 0, 1)
|
||||
# Multiple cycles with the same parameters
|
||||
if forced_inactive_time == 0:
|
||||
return CycleInfo(active_time, 0, cycles_until_reload)
|
||||
# Multiple cycles with different parameters
|
||||
return CycleSequence((
|
||||
CycleInfo(active_time, forced_inactive_time, early_cycles),
|
||||
CycleInfo(active_time, 0, final_cycles)
|
||||
), 1)
|
||||
# Module cycles the same way all the time in 3 cases:
|
||||
# 1) caller doesn't want to take into account reload time
|
||||
# 2) effect does not have to reload anything to keep running
|
||||
# 3) effect has enough time to reload during inactivity periods
|
||||
if (
|
||||
not factorReload or
|
||||
cycles_until_reload == math.inf or
|
||||
forced_inactive_time >= reload_time
|
||||
):
|
||||
return CycleInfo(active_time, forced_inactive_time, math.inf)
|
||||
# We've got to take reload into consideration
|
||||
else:
|
||||
"""
|
||||
Currently no other modules would have a reactivation delay, so for sanities sake don't try and account for it.
|
||||
Okay, technically cloaks do, but they also have 0 cycle time and cap usage so why do you care?
|
||||
"""
|
||||
effective_reload_time = raw_reload_time
|
||||
|
||||
if numShots > 0 and self.charge:
|
||||
speed = (speed * numShots + effective_reload_time) / numShots
|
||||
|
||||
return speed
|
||||
final_cycles = 1
|
||||
early_cycles = cycles_until_reload - final_cycles
|
||||
# If effect has to reload after each its cycle, then its parameters
|
||||
# are the same all the time
|
||||
if early_cycles == 0:
|
||||
return CycleInfo(active_time, reload_time, math.inf)
|
||||
return CycleSequence((
|
||||
CycleInfo(active_time, forced_inactive_time, early_cycles),
|
||||
CycleInfo(active_time, reload_time, final_cycles)
|
||||
), math.inf)
|
||||
|
||||
@property
|
||||
def rawCycleTime(self):
|
||||
@@ -864,7 +929,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def capUse(self):
|
||||
capNeed = self.getModifiedItemAttr("capacitorNeed")
|
||||
if capNeed and self.state >= FittingModuleState.ACTIVE:
|
||||
cycleTime = self.cycleTime
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return 0
|
||||
cycleTime = cycleParams.averageTime
|
||||
if cycleTime > 0:
|
||||
capUsed = capNeed / (cycleTime / 1000.0)
|
||||
return capUsed
|
||||
@@ -873,7 +941,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
@staticmethod
|
||||
def getProposedState(mod, click, proposedState=None):
|
||||
# todo: instead of passing in module, make this a instanced function.
|
||||
pyfalog.debug("Get proposed state for module.")
|
||||
if mod.slot == FittingSlot.SUBSYSTEM or mod.isEmpty:
|
||||
return FittingModuleState.ONLINE
|
||||
@@ -893,13 +960,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
state = FittingModuleState.OFFLINE
|
||||
else:
|
||||
state = transitionMap[currState]
|
||||
if not mod.isValidState(state):
|
||||
state = -1
|
||||
# If passive module tries to transition into online and fails,
|
||||
# put it to passive instead
|
||||
if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
|
||||
state = FittingModuleState.OFFLINE
|
||||
|
||||
if mod.isValidState(state):
|
||||
return state
|
||||
else:
|
||||
return currState
|
||||
return mod.getMaxState(proposedState=state)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
item = self.item
|
||||
@@ -909,6 +975,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
copy = Module(self.item, self.baseItem, self.mutaplasmid)
|
||||
copy.charge = self.charge
|
||||
copy.state = self.state
|
||||
copy.spoolType = self.spoolType
|
||||
copy.spoolAmount = self.spoolAmount
|
||||
|
||||
for x in self.mutators.values():
|
||||
Mutator(copy, x.attribute, x.value)
|
||||
|
||||
@@ -38,14 +38,13 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
"maxTargetsLockedFromSkills": 2,
|
||||
"droneControlRange": 20000,
|
||||
"cloaked": False,
|
||||
"siege": False
|
||||
# We also have speedLimit for Entosis Link, but there seems to be an
|
||||
# issue with naming it exactly "speedLimit" due to unknown reasons.
|
||||
# Regardless, we don't have to put it here anyways - it will come up
|
||||
# as None unless the Entosis effect sets it.
|
||||
}
|
||||
|
||||
def __init__(self, item, parent=None):
|
||||
def __init__(self, item, owner=None):
|
||||
self.validate(item)
|
||||
|
||||
self.__item = item
|
||||
@@ -58,9 +57,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
if "maximumRangeCap" in self.__itemModifiedAttributes.original:
|
||||
cappingAttrKeyCache["maxTargetRange"] = "maximumRangeCap"
|
||||
|
||||
# there are occasions when we need to get to the parent fit of the ship, such as when we need the character
|
||||
# skills for ship-role gang boosts (Titans)
|
||||
self.parent = parent
|
||||
self.owner = owner
|
||||
self.commandBonus = 0
|
||||
|
||||
def validate(self, item):
|
||||
@@ -103,7 +100,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
fit.register(self)
|
||||
effect.handler(fit, self, ("ship",))
|
||||
|
||||
def validateModeItem(self, item):
|
||||
def validateModeItem(self, item, owner=None):
|
||||
""" Checks if provided item is a valid mode """
|
||||
items = self.__modeItems
|
||||
|
||||
@@ -111,7 +108,7 @@ class Ship(ItemAttrShortcut, HandledItem):
|
||||
# if we have items, then we are in a tactical destroyer and must have a mode
|
||||
if item is None or item not in items:
|
||||
# If provided item is invalid mode, force new one
|
||||
return Mode(items[0])
|
||||
return Mode(items[0], owner=owner)
|
||||
return Mode(item)
|
||||
return None
|
||||
|
||||
|
||||
68
eos/utils/cycles.py
Normal file
68
eos/utils/cycles.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# Borrowed from new eos
|
||||
|
||||
|
||||
from utils.repr import makeReprStr
|
||||
|
||||
|
||||
class CycleInfo:
|
||||
|
||||
def __init__(self, activeTime, inactiveTime, quantity):
|
||||
self.activeTime = activeTime
|
||||
self.inactiveTime = inactiveTime
|
||||
self.quantity = quantity
|
||||
|
||||
@property
|
||||
def averageTime(self):
|
||||
return self.activeTime + self.inactiveTime
|
||||
|
||||
def iterCycles(self):
|
||||
i = 0
|
||||
while i < self.quantity:
|
||||
yield self.activeTime, self.inactiveTime
|
||||
i += 1
|
||||
|
||||
def _getCycleQuantity(self):
|
||||
return self.quantity
|
||||
|
||||
def _getTime(self):
|
||||
return (self.activeTime + self.inactiveTime) * self.quantity
|
||||
|
||||
def __repr__(self):
|
||||
spec = ['activeTime', 'inactiveTime', 'quantity']
|
||||
return makeReprStr(self, spec)
|
||||
|
||||
|
||||
class CycleSequence:
|
||||
|
||||
def __init__(self, sequence, quantity):
|
||||
self.sequence = sequence
|
||||
self.quantity = quantity
|
||||
|
||||
@property
|
||||
def averageTime(self):
|
||||
"""Get average time between cycles."""
|
||||
return self._getTime() / self._getCycleQuantity()
|
||||
|
||||
def iterCycles(self):
|
||||
i = 0
|
||||
while i < self.quantity:
|
||||
for cycleInfo in self.sequence:
|
||||
for cycleTime, inactiveTime in cycleInfo.iterCycles():
|
||||
yield cycleTime, inactiveTime
|
||||
i += 1
|
||||
|
||||
def _getCycleQuantity(self):
|
||||
quantity = 0
|
||||
for item in self.sequence:
|
||||
quantity += item._getCycleQuantity()
|
||||
return quantity
|
||||
|
||||
def _getTime(self):
|
||||
time = 0
|
||||
for item in self.sequence:
|
||||
time += item._getTime()
|
||||
return time
|
||||
|
||||
def __repr__(self):
|
||||
spec = ['sequence', 'quantity']
|
||||
return makeReprStr(self, spec)
|
||||
@@ -34,6 +34,7 @@ from gui.toggle_panel import TogglePanel
|
||||
|
||||
|
||||
class AdditionsPane(TogglePanel):
|
||||
|
||||
def __init__(self, parent):
|
||||
|
||||
TogglePanel.__init__(self, parent, force_layout=1)
|
||||
@@ -86,8 +87,8 @@ class AdditionsPane(TogglePanel):
|
||||
|
||||
PANES = ["Drones", "Fighters", "Cargo", "Implants", "Boosters", "Projected", "Command", "Notes"]
|
||||
|
||||
def select(self, name):
|
||||
self.notebook.SetSelection(self.PANES.index(name))
|
||||
def select(self, name, focus=True):
|
||||
self.notebook.SetSelection(self.PANES.index(name), focus=focus)
|
||||
|
||||
def getName(self, idx):
|
||||
return self.PANES[idx]
|
||||
|
||||
@@ -50,8 +50,11 @@ class BitmapLoader(object):
|
||||
|
||||
@classmethod
|
||||
def getStaticBitmap(cls, name, parent, location):
|
||||
bitmap = cls.getBitmap(name or 0, location)
|
||||
if bitmap is None:
|
||||
return None
|
||||
static = wx.StaticBitmap(parent)
|
||||
static.SetBitmap(cls.getBitmap(name or 0, location))
|
||||
static.SetBitmap(bitmap)
|
||||
return static
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED, ItemSelected
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
import gui.fitCommands as cmd
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class BoosterViewDrop(wx.DropTarget):
|
||||
@@ -46,6 +48,7 @@ class BoosterViewDrop(wx.DropTarget):
|
||||
|
||||
|
||||
class BoosterView(d.Display):
|
||||
|
||||
DEFAULT_COLS = [
|
||||
"State",
|
||||
"attr:boosterness",
|
||||
@@ -56,23 +59,20 @@ class BoosterView(d.Display):
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.SetDropTarget(BoosterViewDrop(self.handleListDrag))
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
def handleListDrag(self, x, y, data):
|
||||
"""
|
||||
@@ -88,11 +88,14 @@ class BoosterView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
self.removeBooster(self.boosters[self.GetItemData(row)])
|
||||
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
boosters = self.getSelectedBoosters()
|
||||
self.removeBoosters(boosters)
|
||||
event.Skip()
|
||||
|
||||
def fitChanged(self, event):
|
||||
@@ -113,7 +116,6 @@ class BoosterView(d.Display):
|
||||
if self.boosters is not None:
|
||||
self.boosters.sort(key=lambda booster: booster.slot or 0)
|
||||
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
|
||||
@@ -122,59 +124,101 @@ class BoosterView(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
self.populate(self.boosters)
|
||||
self.refresh(self.boosters)
|
||||
self.update(self.boosters)
|
||||
event.Skip()
|
||||
|
||||
def addItem(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
item = Market.getInstance().getItem(event.itemID, eager='group')
|
||||
if item is None or not item.isBooster:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fit = sFit.getFit(fitID)
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
|
||||
if not fit or fit.isStructure:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddBoosterCommand(fitID, event.itemID)):
|
||||
self.mainFrame.additionsPane.select("Boosters")
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiAddBoosterCommand(fitID=fitID, itemID=event.itemID))
|
||||
# Select in any case - as we might've added booster which has been there already and command failed
|
||||
self.mainFrame.additionsPane.select('Boosters')
|
||||
event.Skip()
|
||||
|
||||
def removeItem(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
self.removeBooster(self.boosters[self.GetItemData(row)])
|
||||
try:
|
||||
booster = self.boosters[row]
|
||||
except IndexError:
|
||||
return
|
||||
self.removeBoosters([booster])
|
||||
|
||||
def removeBooster(self, booster):
|
||||
def removeBoosters(self, boosters):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveBoosterCommand(fitID, self.original.index(booster)))
|
||||
positions = []
|
||||
for booster in boosters:
|
||||
if booster in self.original:
|
||||
positions.append(self.original.index(booster))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveBoostersCommand(fitID=fitID, positions=positions))
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleBoosterCommand(fitID, row))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
try:
|
||||
mainBooster = self.boosters[mainRow]
|
||||
except IndexError:
|
||||
return
|
||||
if mainBooster in self.original:
|
||||
mainPosition = self.original.index(mainBooster)
|
||||
positions = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
booster = self.boosters[row]
|
||||
except IndexError:
|
||||
continue
|
||||
if booster in self.original:
|
||||
positions.append(self.original.index(booster))
|
||||
if mainPosition not in positions:
|
||||
positions = [mainPosition]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleBoosterStatesCommand(
|
||||
fitID=fitID,
|
||||
mainPosition=mainPosition,
|
||||
positions=positions))
|
||||
return
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
sel = self.GetFirstSelected()
|
||||
if sel != -1:
|
||||
sFit = Fit.getInstance()
|
||||
item = self.boosters[sel]
|
||||
|
||||
srcContext = "boosterItem"
|
||||
itemContext = "Booster"
|
||||
menu = ContextMenu.getMenu((item,), (srcContext, itemContext))
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedBoosters()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainBooster = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
booster = self.boosters[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if booster in self.original:
|
||||
mainBooster = booster
|
||||
sourceContext = "boosterItem"
|
||||
itemContext = None if mainBooster is None else "Booster"
|
||||
menu = ContextMenu.getMenu(mainBooster, selection, (sourceContext, itemContext))
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getSelectedBoosters(self):
|
||||
boosters = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
booster = self.boosters[row]
|
||||
except IndexError:
|
||||
continue
|
||||
boosters.append(booster)
|
||||
return boosters
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.display as d
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class CargoViewDrop(wx.DropTarget):
|
||||
@@ -53,21 +53,18 @@ class CargoView(d.Display):
|
||||
"Price"]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.SetDropTarget(CargoViewDrop(self.handleListDrag))
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
def handleListDrag(self, x, y, data):
|
||||
"""
|
||||
@@ -81,18 +78,25 @@ class CargoView(d.Display):
|
||||
if data[0] == "fitting":
|
||||
self.swapModule(x, y, int(data[1]))
|
||||
elif data[0] == "market":
|
||||
fit = self.mainFrame.getActiveFit()
|
||||
if fit:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(fit, int(data[1])))
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(
|
||||
fitID=fitID, itemID=int(data[1]), amount=1))
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
|
||||
if row != -1:
|
||||
data = wx.TextDataObject()
|
||||
dataStr = "cargo:" + str(row)
|
||||
try:
|
||||
dataStr = "cargo:{}".format(self.cargo[row].itemID)
|
||||
except IndexError:
|
||||
return
|
||||
data.SetText(dataStr)
|
||||
|
||||
self.unselectAll()
|
||||
self.Select(row, True)
|
||||
|
||||
dropSource = wx.DropSource(self)
|
||||
dropSource.SetData(data)
|
||||
DragDropHelper.data = dataStr
|
||||
@@ -100,13 +104,14 @@ class CargoView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
sFit.removeCargo(fitID, self.GetItemData(row))
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
cargos = self.getSelectedCargos()
|
||||
self.removeCargos(cargos)
|
||||
event.Skip()
|
||||
|
||||
def swapModule(self, x, y, modIdx):
|
||||
@@ -114,28 +119,20 @@ class CargoView(d.Display):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
mstate = wx.GetMouseState()
|
||||
|
||||
# Gather module information to get position
|
||||
module = fit.modules[modIdx]
|
||||
if dstRow > -1:
|
||||
try:
|
||||
dstCargoItemID = getattr(self.cargo[dstRow], 'itemID', None)
|
||||
except IndexError:
|
||||
dstCargoItemID = None
|
||||
else:
|
||||
dstCargoItemID = None
|
||||
|
||||
if module.item.isAbyssal:
|
||||
dlg = wx.MessageDialog(self,
|
||||
"Moving this Abyssal module to the cargo will convert it to the base module. Do you wish to proceed?",
|
||||
"Confirm", wx.YES_NO | wx.ICON_QUESTION)
|
||||
result = dlg.ShowModal() == wx.ID_YES
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
||||
cargoPos = dstRow if dstRow > -1 else None
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiModuleToCargoCommand(
|
||||
self.mainFrame.getActiveFit(),
|
||||
module.modPosition,
|
||||
cargoPos,
|
||||
mstate.cmdDown
|
||||
))
|
||||
self.mainFrame.command.Submit(cmd.GuiLocalModuleToCargoCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
modPosition=modIdx,
|
||||
cargoItemID=dstCargoItemID,
|
||||
copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL))
|
||||
|
||||
def fitChanged(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
@@ -151,9 +148,9 @@ class CargoView(d.Display):
|
||||
return
|
||||
|
||||
self.original = fit.cargo if fit is not None else None
|
||||
self.cargo = stuff = fit.cargo if fit is not None else None
|
||||
if stuff is not None:
|
||||
stuff.sort(key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name))
|
||||
self.cargo = fit.cargo[:] if fit is not None else None
|
||||
if self.cargo is not None:
|
||||
self.cargo.sort(key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name))
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
@@ -163,38 +160,53 @@ class CargoView(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
self.populate(stuff)
|
||||
self.refresh(stuff)
|
||||
self.populate(self.cargo)
|
||||
self.refresh(self.cargo)
|
||||
event.Skip()
|
||||
|
||||
def removeItem(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
cargo = self.cargo[self.GetItemData(row)]
|
||||
sFit.removeCargo(fitID, self.original.index(cargo))
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
try:
|
||||
cargo = self.cargo[row]
|
||||
except IndexError:
|
||||
return
|
||||
self.removeCargos([cargo])
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
def removeCargos(self, cargos):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
itemIDs = []
|
||||
for cargo in cargos:
|
||||
if cargo in self.original:
|
||||
itemIDs.append(cargo.itemID)
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCargosCommand(fitID=fitID, itemIDs=itemIDs))
|
||||
|
||||
def spawnMenu(self):
|
||||
sel = self.GetFirstSelected()
|
||||
if sel != -1:
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
cargo = fit.cargo[sel]
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
sourceContext = "cargoItem"
|
||||
itemContext = sMkt.getCategoryByItem(cargo.item).name
|
||||
|
||||
menu = ContextMenu.getMenu((cargo,), (sourceContext, itemContext))
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedCargos()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainCargo = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
cargo = self.cargo[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if cargo in self.original:
|
||||
mainCargo = cargo
|
||||
sourceContext = "cargoItem"
|
||||
itemContext = None if mainCargo is None else Market.getInstance().getCategoryByItem(mainCargo.item).name
|
||||
menu = ContextMenu.getMenu(mainCargo, selection, (sourceContext, itemContext))
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getSelectedCargos(self):
|
||||
cargos = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
cargo = self.cargo[row]
|
||||
except IndexError:
|
||||
continue
|
||||
cargos.append(cargo)
|
||||
return cargos
|
||||
|
||||
@@ -22,24 +22,25 @@ import wx
|
||||
|
||||
import gui.builtinAdditionPanes.droneView
|
||||
import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from gui.builtinContextMenus.commandFitAdd import AddCommandFit
|
||||
from gui.builtinShipBrowser.events import EVT_FIT_REMOVED
|
||||
from eos.saveddata.drone import Drone as es_Drone
|
||||
from gui.builtinContextMenus.commandFits import CommandFits
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class DummyItem(object):
|
||||
class DummyItem:
|
||||
|
||||
def __init__(self, txt):
|
||||
self.name = txt
|
||||
self.iconID = None
|
||||
|
||||
|
||||
class DummyEntry(object):
|
||||
class DummyEntry:
|
||||
|
||||
def __init__(self, txt):
|
||||
self.item = DummyItem(txt)
|
||||
|
||||
@@ -61,29 +62,25 @@ class CommandViewDrop(wx.DropTarget):
|
||||
|
||||
|
||||
class CommandView(d.Display):
|
||||
|
||||
DEFAULT_COLS = ["State", "Base Name"]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, CommandFits.fitChanged)
|
||||
self.mainFrame.Bind(EVT_FIT_REMOVED, CommandFits.populateFits)
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, AddCommandFit.fitChanged)
|
||||
self.mainFrame.Bind(EVT_FIT_REMOVED, AddCommandFit.populateFits)
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.remove)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.droneView = gui.builtinAdditionPanes.droneView.DroneView
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
self.SetDropTarget(CommandViewDrop(self.handleListDrag))
|
||||
|
||||
@staticmethod
|
||||
@@ -99,30 +96,22 @@ class CommandView(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCommandCommand(fitID, self.get(row).ID))
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
commandFits = self.getSelectedCommandFits()
|
||||
self.removeCommandFits(commandFits)
|
||||
event.Skip()
|
||||
|
||||
def handleDrag(self, type, fitID):
|
||||
# Those are drags coming from pyfa sources, NOT builtin wx drags
|
||||
if type == "fit":
|
||||
activeFit = self.mainFrame.getActiveFit()
|
||||
if activeFit:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandCommand(activeFit, fitID))
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
if row != -1 and isinstance(self.get(row), es_Drone):
|
||||
data = wx.TextDataObject()
|
||||
dataStr = "command:" + str(self.GetItemData(row))
|
||||
data.SetText(dataStr)
|
||||
|
||||
dropSource = wx.DropSource(self)
|
||||
dropSource.SetData(data)
|
||||
DragDropHelper.data = dataStr
|
||||
dropSource.DoDragDrop()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitCommand(fitID=activeFit, commandFitID=fitID))
|
||||
|
||||
@staticmethod
|
||||
def fitSort(fit):
|
||||
@@ -132,7 +121,7 @@ class CommandView(d.Display):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(event.fitID)
|
||||
|
||||
CommandFits.populateFits(event)
|
||||
AddCommandFit.populateFits(event)
|
||||
|
||||
self.Parent.Parent.DisablePage(self, not fit or fit.isStructure)
|
||||
|
||||
@@ -157,9 +146,8 @@ class CommandView(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
# todo: verify
|
||||
if not stuff:
|
||||
stuff = [DummyEntry("Drag a fit to this area")]
|
||||
|
||||
@@ -167,57 +155,68 @@ class CommandView(d.Display):
|
||||
|
||||
event.Skip()
|
||||
|
||||
def get(self, row):
|
||||
if row == -1:
|
||||
return None
|
||||
|
||||
numFits = len(self.fits)
|
||||
|
||||
if numFits == 0:
|
||||
return None
|
||||
|
||||
return self.fits[row]
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
item = self.get(row)
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleCommandCommand(fitID, item.ID))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
try:
|
||||
mainCommandFitID = self.fits[mainRow].ID
|
||||
except IndexError:
|
||||
return
|
||||
commandFitIDs = []
|
||||
for commandFit in self.getSelectedCommandFits():
|
||||
commandFitIDs.append(commandFit.ID)
|
||||
if mainCommandFitID not in commandFitIDs:
|
||||
commandFitIDs = [mainCommandFitID]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleCommandFitStatesCommand(
|
||||
fitID=fitID,
|
||||
mainCommandFitID=mainCommandFitID,
|
||||
commandFitIDs=commandFitIDs))
|
||||
return
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return
|
||||
|
||||
sel = self.GetFirstSelected()
|
||||
context = ()
|
||||
item = self.get(sel)
|
||||
|
||||
if item is not None:
|
||||
fitSrcContext = "commandFit"
|
||||
fitItemContext = item.name
|
||||
context = ((fitSrcContext, fitItemContext),)
|
||||
|
||||
context += (("commandView",),)
|
||||
menu = ContextMenu.getMenu((item,) if item is not None else [], *context)
|
||||
if menu is not None:
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedCommandFits()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainCommandFit = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
mainCommandFit = self.fits[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
contexts = []
|
||||
if mainCommandFit is not None:
|
||||
contexts.append(('commandFit', 'Command Fit'))
|
||||
contexts.append(('commandView',))
|
||||
menu = ContextMenu.getMenu(mainCommandFit, selection, *contexts)
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def remove(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
thing = self.get(row)
|
||||
if thing: # thing doesn't exist if it's the dummy value
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCommandCommand(fitID, thing.ID))
|
||||
try:
|
||||
commandFit = self.fits[row]
|
||||
except IndexError:
|
||||
return
|
||||
self.removeCommandFits([commandFit])
|
||||
|
||||
def removeCommandFits(self, commandFits):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
commandFitIDs = []
|
||||
for commandFit in commandFits:
|
||||
if commandFit in self.fits:
|
||||
commandFitIDs.append(commandFit.ID)
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCommandFitsCommand(fitID=fitID, commandFitIDs=commandFitIDs))
|
||||
|
||||
def getSelectedCommandFits(self):
|
||||
commandFits = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
commandFit = self.fits[row]
|
||||
except IndexError:
|
||||
continue
|
||||
commandFits.append(commandFit)
|
||||
return commandFits
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
@@ -30,6 +33,7 @@ from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
from gui.fitCommands.helpers import droneStackLimit
|
||||
|
||||
|
||||
class DroneViewDrop(wx.DropTarget):
|
||||
@@ -61,7 +65,7 @@ class DroneView(Display):
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
@@ -72,16 +76,13 @@ class DroneView(Display):
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
||||
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
self.SetDropTarget(DroneViewDrop(self.handleDragDrop))
|
||||
@@ -101,7 +102,10 @@ class DroneView(Display):
|
||||
self.hoveredRow = row
|
||||
self.hoveredColumn = col
|
||||
if row != -1 and col != -1 and col < len(self.DEFAULT_COLS):
|
||||
mod = self.drones[self.GetItemData(row)]
|
||||
try:
|
||||
mod = self.drones[row]
|
||||
except IndexError:
|
||||
return
|
||||
if self.DEFAULT_COLS[col] == "Miscellanea":
|
||||
tooltip = self.activeColumns[col].getToolTip(mod)
|
||||
if tooltip is not None:
|
||||
@@ -116,17 +120,22 @@ class DroneView(Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
drone = self.drones[self.GetItemData(row)]
|
||||
self.removeDrone(drone)
|
||||
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
drones = self.getSelectedDrones()
|
||||
self.removeDroneStacks(drones)
|
||||
event.Skip()
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
if row != -1:
|
||||
self.unselectAll()
|
||||
self.Select(row, True)
|
||||
|
||||
data = wx.TextDataObject()
|
||||
dataStr = "drone:" + str(row)
|
||||
data.SetText(dataStr)
|
||||
@@ -144,22 +153,38 @@ class DroneView(Display):
|
||||
data[0] is hard-coded str of originating source
|
||||
data[1] is typeID or index of data we want to manipulate
|
||||
"""
|
||||
if data[0] == "drone": # we want to merge drones
|
||||
pass
|
||||
# remove merge functionality, if people complain in the next while, can add it back
|
||||
# srcRow = int(data[1])
|
||||
# dstRow, _ = self.HitTest((x, y))
|
||||
# if srcRow != -1 and dstRow != -1:
|
||||
# self._merge(srcRow, dstRow)
|
||||
if data[0] == "drone":
|
||||
srcRow = int(data[1])
|
||||
if srcRow != -1:
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL:
|
||||
try:
|
||||
srcDrone = self.drones[srcRow]
|
||||
except IndexError:
|
||||
return
|
||||
if srcDrone not in self.original:
|
||||
return
|
||||
self.mainFrame.command.Submit(cmd.GuiCloneLocalDroneCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
position=self.original.index(srcDrone)))
|
||||
else:
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
if dstRow != -1:
|
||||
self._merge(srcRow, dstRow)
|
||||
elif data[0] == "market":
|
||||
wx.PostEvent(self.mainFrame, ItemSelected(itemID=int(data[1])))
|
||||
|
||||
def _merge(self, src, dst):
|
||||
sFit = Fit.getInstance()
|
||||
def _merge(self, srcRow, dstRow):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
if sFit.mergeDrones(fitID, self.drones[src], self.drones[dst]):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
try:
|
||||
srcDrone = self.drones[srcRow]
|
||||
dstDrone = self.drones[dstRow]
|
||||
except IndexError:
|
||||
return
|
||||
if srcDrone in self.original and dstDrone in self.original:
|
||||
srcPosition = self.original.index(srcDrone)
|
||||
dstPosition = self.original.index(dstDrone)
|
||||
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
||||
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
@@ -187,10 +212,10 @@ class DroneView(Display):
|
||||
return
|
||||
|
||||
self.original = fit.drones if fit is not None else None
|
||||
self.drones = stuff = fit.drones[:] if fit is not None else None
|
||||
self.drones = fit.drones[:] if fit is not None else None
|
||||
|
||||
if stuff is not None:
|
||||
stuff.sort(key=self.droneKey)
|
||||
if self.drones is not None:
|
||||
self.drones.sort(key=self.droneKey)
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
@@ -200,60 +225,111 @@ class DroneView(Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
self.update(stuff)
|
||||
self.update(self.drones)
|
||||
event.Skip()
|
||||
|
||||
def addItem(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
item = Market.getInstance().getItem(event.itemID, eager='group.category')
|
||||
if item is None or not item.isDrone:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if not fit or fit.isStructure:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddDroneCommand(fitID, event.itemID)):
|
||||
self.mainFrame.additionsPane.select("Drones")
|
||||
amount = droneStackLimit(fit, event.itemID) if wx.GetMouseState().GetModifiers() == wx.MOD_ALT else 1
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddLocalDroneCommand(fitID=fitID, itemID=event.itemID, amount=amount)):
|
||||
self.mainFrame.additionsPane.select('Drones')
|
||||
|
||||
event.Skip()
|
||||
|
||||
def removeItem(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
drone = self.drones[self.GetItemData(row)]
|
||||
self.removeDrone(drone)
|
||||
try:
|
||||
drone = self.drones[row]
|
||||
except IndexError:
|
||||
return
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
self.removeDroneStacks([drone])
|
||||
else:
|
||||
self.removeDrone(drone)
|
||||
|
||||
def removeDrone(self, drone):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveDroneCommand(fitID, self.original.index(drone)))
|
||||
if drone in self.original:
|
||||
position = self.original.index(drone)
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalDronesCommand(
|
||||
fitID=fitID, positions=[position], amount=1))
|
||||
|
||||
def removeDroneStacks(self, drones):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
positions = []
|
||||
for drone in drones:
|
||||
if drone in self.original:
|
||||
positions.append(self.original.index(drone))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalDronesCommand(
|
||||
fitID=fitID, positions=positions, amount=math.inf))
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
drone = self.drones[row]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleDroneCommand(fitID, self.original.index(drone)))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
try:
|
||||
mainDrone = self.drones[mainRow]
|
||||
except IndexError:
|
||||
return
|
||||
if mainDrone in self.original:
|
||||
mainPosition = self.original.index(mainDrone)
|
||||
positions = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
drone = self.drones[row]
|
||||
except IndexError:
|
||||
continue
|
||||
if drone in self.original:
|
||||
positions.append(self.original.index(drone))
|
||||
if mainPosition not in positions:
|
||||
positions = [mainPosition]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleLocalDroneStatesCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
mainPosition=mainPosition,
|
||||
positions=positions))
|
||||
return
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
sel = self.GetFirstSelected()
|
||||
if sel != -1:
|
||||
drone = self.drones[sel]
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
sourceContext = "droneItem"
|
||||
itemContext = sMkt.getCategoryByItem(drone.item).name
|
||||
menu = ContextMenu.getMenu((drone,), (sourceContext, itemContext))
|
||||
def spawnMenu(self, event):
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainDrone = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
drone = self.drones[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if drone in self.original:
|
||||
mainDrone = drone
|
||||
selection = self.getSelectedDrones()
|
||||
sourceContext = "droneItem"
|
||||
itemContext = None if mainDrone is None else Market.getInstance().getCategoryByItem(mainDrone.item).name
|
||||
menu = ContextMenu.getMenu(mainDrone, selection, (sourceContext, itemContext))
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getSelectedDrones(self):
|
||||
drones = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
drone = self.drones[row]
|
||||
except IndexError:
|
||||
continue
|
||||
drones.append(drone)
|
||||
return drones
|
||||
|
||||
@@ -20,17 +20,18 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED
|
||||
import gui.mainFrame
|
||||
import gui.display as d
|
||||
from gui.builtinViewColumns.state import State
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingSlot
|
||||
from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.fitCommands.helpers import getSimilarFighters
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class FighterViewDrop(wx.DropTarget):
|
||||
@@ -50,6 +51,7 @@ class FighterViewDrop(wx.DropTarget):
|
||||
|
||||
|
||||
class FighterView(wx.Panel):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
@@ -115,6 +117,7 @@ class FighterView(wx.Panel):
|
||||
|
||||
|
||||
class FighterDisplay(d.Display):
|
||||
|
||||
DEFAULT_COLS = ["State",
|
||||
# "Base Icon",
|
||||
"Base Name",
|
||||
@@ -127,7 +130,7 @@ class FighterDisplay(d.Display):
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
@@ -136,16 +139,13 @@ class FighterDisplay(d.Display):
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
||||
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
self.SetDropTarget(FighterViewDrop(self.handleDragDrop))
|
||||
@@ -165,7 +165,10 @@ class FighterDisplay(d.Display):
|
||||
self.hoveredRow = row
|
||||
self.hoveredColumn = col
|
||||
if row != -1 and col != -1 and col < len(self.DEFAULT_COLS):
|
||||
mod = self.fighters[self.GetItemData(row)]
|
||||
try:
|
||||
mod = self.fighters[row]
|
||||
except IndexError:
|
||||
return
|
||||
if self.DEFAULT_COLS[col] == "Miscellanea":
|
||||
tooltip = self.activeColumns[col].getToolTip(mod)
|
||||
if tooltip is not None:
|
||||
@@ -180,17 +183,22 @@ class FighterDisplay(d.Display):
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
fighter = self.fighters[self.GetItemData(row)]
|
||||
self.removeFighter(fighter)
|
||||
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
fighters = self.getSelectedFighters()
|
||||
self.removeFighters(fighters)
|
||||
event.Skip()
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
if row != -1:
|
||||
self.unselectAll()
|
||||
self.Select(row, True)
|
||||
|
||||
data = wx.TextDataObject()
|
||||
dataStr = "fighter:" + str(row)
|
||||
data.SetText(dataStr)
|
||||
@@ -220,12 +228,18 @@ class FighterDisplay(d.Display):
|
||||
def _merge(src, dst):
|
||||
return
|
||||
|
||||
FIGHTER_ORDER = ('Heavy Fighter', 'Light Fighter', 'Support Fighter')
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
def fighterKey(self, fighter):
|
||||
sMkt = Market.getInstance()
|
||||
groupName = sMkt.getGroupByItem(fighter.item).name
|
||||
return (self.FIGHTER_ORDER.index(groupName), fighter.item.name)
|
||||
groupName = Market.getInstance().getGroupByItem(fighter.item).name
|
||||
orderPos = self.FIGHTER_ORDER.index(groupName)
|
||||
# Sort support fighters by name, ignore their abilities
|
||||
if groupName == 'Support Fighter':
|
||||
abilityEffectIDs = ()
|
||||
# Group up fighters from various roles
|
||||
else:
|
||||
abilityEffectIDs = sorted(a.effectID for a in fighter.abilities)
|
||||
return orderPos, abilityEffectIDs, fighter.item.name
|
||||
|
||||
def fitChanged(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
@@ -254,53 +268,104 @@ class FighterDisplay(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
self.update(self.fighters)
|
||||
event.Skip()
|
||||
|
||||
def addItem(self, event):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
item = Market.getInstance().getItem(event.itemID, eager='group.category')
|
||||
if item is None or not item.isFighter:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddFighterCommand(fitID, event.itemID)):
|
||||
self.mainFrame.additionsPane.select("Fighters")
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddLocalFighterCommand(fitID, event.itemID)):
|
||||
self.mainFrame.additionsPane.select('Fighters')
|
||||
|
||||
event.Skip()
|
||||
|
||||
def removeItem(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
fighter = self.fighters[self.GetItemData(row)]
|
||||
self.removeFighter(fighter)
|
||||
mstate = wx.GetMouseState()
|
||||
try:
|
||||
fighter = self.fighters[row]
|
||||
except IndexError:
|
||||
return
|
||||
if mstate.GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(self.original, fighter)
|
||||
else:
|
||||
fighters = [fighter]
|
||||
self.removeFighters(fighters)
|
||||
|
||||
def removeFighter(self, fighter):
|
||||
def removeFighters(self, fighters):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveFighterCommand(fitID, self.original.index(fighter)))
|
||||
positions = []
|
||||
for fighter in fighters:
|
||||
if fighter in self.original:
|
||||
positions.append(self.original.index(fighter))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalFightersCommand(fitID=fitID, positions=positions))
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fighter = self.fighters[row]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleFighterCommand(fitID, self.original.index(fighter)))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
try:
|
||||
mainFighter = self.fighters[mainRow]
|
||||
except IndexError:
|
||||
return
|
||||
if mainFighter in self.original:
|
||||
mainPosition = self.original.index(mainFighter)
|
||||
positions = []
|
||||
if event.GetModifiers() == wx.MOD_ALT:
|
||||
for fighter in getSimilarFighters(self.original, mainFighter):
|
||||
positions.append(self.original.index(fighter))
|
||||
else:
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
fighter = self.fighters[row]
|
||||
except IndexError:
|
||||
continue
|
||||
if fighter in self.original:
|
||||
positions.append(self.original.index(fighter))
|
||||
if mainPosition not in positions:
|
||||
positions = [mainPosition]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleLocalFighterStatesCommand(
|
||||
fitID=fitID,
|
||||
mainPosition=mainPosition,
|
||||
positions=positions))
|
||||
return
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
sel = self.GetFirstSelected()
|
||||
if sel != -1:
|
||||
fighter = self.fighters[sel]
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
sourceContext = "fighterItem"
|
||||
itemContext = sMkt.getCategoryByItem(fighter.item).name
|
||||
menu = ContextMenu.getMenu((fighter,), (sourceContext, itemContext))
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedFighters()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainFighter = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
fighter = self.fighters[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if fighter in self.original:
|
||||
mainFighter = fighter
|
||||
sourceContext = "fighterItem"
|
||||
itemContext = None if mainFighter is None else Market.getInstance().getCategoryByItem(mainFighter.item).name
|
||||
menu = ContextMenu.getMenu(mainFighter, selection, (sourceContext, itemContext))
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getSelectedFighters(self):
|
||||
fighters = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
fighter = self.fighters[row]
|
||||
except IndexError:
|
||||
continue
|
||||
fighters.append(fighter)
|
||||
return fighters
|
||||
|
||||
@@ -19,17 +19,18 @@
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.display as d
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED
|
||||
import gui.mainFrame
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from eos.const import ImplantLocation
|
||||
from gui.builtinMarketBrowser.events import ITEM_SELECTED
|
||||
from gui.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class ImplantViewDrop(wx.DropTarget):
|
||||
@@ -49,6 +50,7 @@ class ImplantViewDrop(wx.DropTarget):
|
||||
|
||||
|
||||
class ImplantView(wx.Panel):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL)
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
@@ -94,10 +96,12 @@ class ImplantView(wx.Panel):
|
||||
def OnRadioSelect(self, event):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is not None:
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeImplantLocation(fitID, ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeImplantLocationCommand(
|
||||
fitID=fitID, source=ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER))
|
||||
|
||||
|
||||
class ImplantDisplay(d.Display):
|
||||
|
||||
DEFAULT_COLS = [
|
||||
"State",
|
||||
"attr:implantness",
|
||||
@@ -107,22 +111,19 @@ class ImplantDisplay(d.Display):
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.mainFrame.Bind(ITEM_SELECTED, self.addItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
self.SetDropTarget(ImplantViewDrop(self.handleListDrag))
|
||||
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
|
||||
def handleListDrag(self, x, y, data):
|
||||
"""
|
||||
@@ -134,15 +135,20 @@ class ImplantDisplay(d.Display):
|
||||
"""
|
||||
|
||||
if data[0] == "market":
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddImplantCommand(self.mainFrame.getActiveFit(), int(data[1]))):
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddImplantCommand(
|
||||
fitID=self.mainFrame.getActiveFit(), itemID=int(data[1]))):
|
||||
self.mainFrame.additionsPane.select("Implants")
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
self.removeImplant(self.implants[self.GetItemData(row)])
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
implants = self.getSelectedImplants()
|
||||
self.removeImplants(implants)
|
||||
event.Skip()
|
||||
|
||||
def fitChanged(self, event):
|
||||
@@ -158,10 +164,10 @@ class ImplantDisplay(d.Display):
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
self.original = fit.implants if fit is not None else None
|
||||
self.implants = stuff = fit.appliedImplants if fit is not None else None
|
||||
if stuff is not None:
|
||||
stuff.sort(key=lambda implant: implant.slot)
|
||||
self.original = fit.appliedImplants if fit is not None else None
|
||||
self.implants = fit.appliedImplants[:] if fit is not None else None
|
||||
if self.implants is not None:
|
||||
self.implants.sort(key=lambda implant: implant.slot or 0)
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
@@ -171,86 +177,112 @@ class ImplantDisplay(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
self.update(stuff)
|
||||
self.update(self.implants)
|
||||
event.Skip()
|
||||
|
||||
def addItem(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
item = Market.getInstance().getItem(event.itemID, eager='group.category')
|
||||
if item is None or not item.isImplant:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fit = sFit.getFit(fitID)
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
|
||||
if not fit or fit.isStructure:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
if self.mainFrame.command.Submit(cmd.GuiAddImplantCommand(fitID, event.itemID)):
|
||||
self.mainFrame.additionsPane.select("Implants")
|
||||
self.mainFrame.command.Submit(cmd.GuiAddImplantCommand(
|
||||
fitID=fitID, itemID=event.itemID))
|
||||
# Select in any case - as we might've added implant which has been there already and command failed
|
||||
self.mainFrame.additionsPane.select('Implants')
|
||||
|
||||
event.Skip()
|
||||
|
||||
def removeItem(self, event):
|
||||
# Character implants can't be changed here...
|
||||
if self.Parent.source == ImplantLocation.CHARACTER:
|
||||
return
|
||||
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
self.removeImplant(self.implants[self.GetItemData(row)])
|
||||
try:
|
||||
implant = self.implants[row]
|
||||
except IndexError:
|
||||
return
|
||||
self.removeImplants([implant])
|
||||
|
||||
def removeImplant(self, implant):
|
||||
def removeImplants(self, implants):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit.implantLocation == ImplantLocation.FIT:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantCommand(fitID, self.original.index(implant)))
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if fit.implantLocation != ImplantLocation.FIT:
|
||||
return
|
||||
positions = []
|
||||
for implant in implants:
|
||||
if implant in self.original:
|
||||
positions.append(self.original.index(implant))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantsCommand(fitID=fitID, positions=positions))
|
||||
|
||||
def click(self, event):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if fit.implantLocation == ImplantLocation.FIT:
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
try:
|
||||
mainImplant = self.implants[mainRow]
|
||||
except IndexError:
|
||||
return
|
||||
if mainImplant in self.original:
|
||||
mainPosition = self.original.index(mainImplant)
|
||||
positions = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
implant = self.implants[row]
|
||||
except IndexError:
|
||||
continue
|
||||
if implant in self.original:
|
||||
positions.append(self.original.index(implant))
|
||||
if mainPosition not in positions:
|
||||
positions = [mainPosition]
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleImplantStatesCommand(
|
||||
fitID=fitID,
|
||||
mainPosition=mainPosition,
|
||||
positions=positions))
|
||||
return
|
||||
event.Skip()
|
||||
|
||||
# Character implants can't be changed here...
|
||||
if self.Parent.source == ImplantLocation.CHARACTER:
|
||||
return
|
||||
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleImplantCommand(fitID, row))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
sel = self.GetFirstSelected()
|
||||
menu = None
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
if not fit:
|
||||
return
|
||||
|
||||
if sel != -1:
|
||||
implant = fit.appliedImplants[sel]
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
sourceContext = "implantItem" if fit.implantSource == ImplantLocation.FIT else "implantItemChar"
|
||||
itemContext = sMkt.getCategoryByItem(implant.item).name
|
||||
|
||||
menu = ContextMenu.getMenu((implant,), (sourceContext, itemContext))
|
||||
elif sel == -1 and fit.implantSource == ImplantLocation.FIT:
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return
|
||||
context = (("implantView",),)
|
||||
menu = ContextMenu.getMenu([], *context)
|
||||
if menu is not None:
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedImplants()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainImplant = None
|
||||
if clickedPos != -1:
|
||||
try:
|
||||
implant = self.implants[clickedPos]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
if implant in self.original:
|
||||
mainImplant = implant
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
sourceContext1 = "implantItem" if fit.implantSource == ImplantLocation.FIT else "implantItemChar"
|
||||
sourceContext2 = "implantView" if fit.implantSource == ImplantLocation.FIT else "implantViewChar"
|
||||
itemContext = None if mainImplant is None else Market.getInstance().getCategoryByItem(mainImplant.item).name
|
||||
menu = ContextMenu.getMenu(mainImplant, selection, (sourceContext1, itemContext), (sourceContext2, itemContext))
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getSelectedImplants(self):
|
||||
implants = []
|
||||
for row in self.getSelectedRows():
|
||||
try:
|
||||
implant = self.implants[row]
|
||||
except IndexError:
|
||||
continue
|
||||
implants.append(implant)
|
||||
return implants
|
||||
|
||||
@@ -8,6 +8,7 @@ from gui.utils.helpers_wxPython import HandleCtrlBackspace
|
||||
|
||||
|
||||
class NotesView(wx.Panel):
|
||||
|
||||
def __init__(self, parent):
|
||||
wx.Panel.__init__(self, parent)
|
||||
self.lastFitId = None
|
||||
|
||||
@@ -17,22 +17,27 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
import math
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import gui.builtinAdditionPanes.droneView
|
||||
import gui.display as d
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
from eos.saveddata.drone import Drone as es_Drone
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.module import Module as es_Module
|
||||
from eos.saveddata.drone import Drone as EosDrone
|
||||
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.builtinViewColumns.state import State
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.fitCommands.helpers import getSimilarFighters, getSimilarModPositions
|
||||
from gui.utils.staticHelpers import DragDropHelper
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -65,31 +70,27 @@ class ProjectedViewDrop(wx.DropTarget):
|
||||
|
||||
|
||||
class ProjectedView(d.Display):
|
||||
DEFAULT_COLS = ["State",
|
||||
"Ammo Icon",
|
||||
"Base Icon",
|
||||
"Base Name",
|
||||
"Ammo"]
|
||||
DEFAULT_COLS = ['State',
|
||||
'Ammo Icon',
|
||||
'Base Icon',
|
||||
'Base Name',
|
||||
'Ammo']
|
||||
|
||||
def __init__(self, parent):
|
||||
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL | wx.BORDER_NONE)
|
||||
d.Display.__init__(self, parent, style=wx.BORDER_NONE)
|
||||
|
||||
self.lastFitId = None
|
||||
|
||||
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.remove)
|
||||
self.Bind(wx.EVT_LEFT_DCLICK, self.onLeftDoubleClick)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
|
||||
self.droneView = gui.builtinAdditionPanes.droneView.DroneView
|
||||
|
||||
if "__WXGTK__" in wx.PlatformInfo:
|
||||
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
|
||||
else:
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
|
||||
self.SetDropTarget(ProjectedViewDrop(self.handleListDrag))
|
||||
|
||||
def handleListDrag(self, x, y, data):
|
||||
@@ -100,74 +101,48 @@ class ProjectedView(d.Display):
|
||||
data[0] is hard-coded str of originating source
|
||||
data[1] is typeID or index of data we want to manipulate
|
||||
"""
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
if data[0] == "projected":
|
||||
# if source is coming from projected, we are trying to combine drones.
|
||||
pass
|
||||
# removing merge functionality - if people complain about it, can add it back as a command
|
||||
# self.mergeDrones(x, y, int(data[1]))
|
||||
elif data[0] == "fitting":
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if data[0] == 'fitting':
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
# Gather module information to get position
|
||||
module = fit.modules[int(data[1])]
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, module.itemID, 'item'))
|
||||
# sFit.project(fit.ID, module)
|
||||
# wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
|
||||
elif data[0] == "market":
|
||||
# sFit = Fit.getInstance()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, int(data[1]), 'item'))
|
||||
# sFit.project(fit.ID, int(data[1]))
|
||||
# wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(
|
||||
fitID=fitID, itemID=fit.modules[int(data[1])].itemID))
|
||||
elif data[0] == 'market':
|
||||
itemID = int(data[1])
|
||||
category = Market.getInstance().getItem(itemID, eager=('group.category')).category.name
|
||||
if category == 'Module':
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Drone':
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedDroneCommand(fitID=fitID, itemID=itemID))
|
||||
elif category == 'Fighter':
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=itemID))
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
row = self.GetFirstSelected()
|
||||
if row != -1:
|
||||
sFit.removeProjected(fitID, self.get(row))
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == wx.WXK_ESCAPE and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.unselectAll()
|
||||
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
items=self.getSelectedProjectors(),
|
||||
amount=math.inf))
|
||||
event.Skip()
|
||||
|
||||
def handleDrag(self, type, fitID):
|
||||
# Those are drags coming from pyfa sources, NOT builtin wx drags
|
||||
if type == "fit":
|
||||
if type == 'fit':
|
||||
activeFit = self.mainFrame.getActiveFit()
|
||||
if activeFit:
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(activeFit, fitID, 'fit'))
|
||||
|
||||
def startDrag(self, event):
|
||||
row = event.GetIndex()
|
||||
if row != -1 and isinstance(self.get(row), es_Drone):
|
||||
data = wx.TextDataObject()
|
||||
dataStr = "projected:" + str(self.GetItemData(row))
|
||||
data.SetText(dataStr)
|
||||
|
||||
dropSource = wx.DropSource(self)
|
||||
dropSource.SetData(data)
|
||||
DragDropHelper.data = dataStr
|
||||
dropSource.DoDragDrop()
|
||||
|
||||
def mergeDrones(self, x, y, itemID):
|
||||
srcRow = self.FindItemData(-1, itemID)
|
||||
dstRow, _ = self.HitTest((x, y))
|
||||
if srcRow != -1 and dstRow != -1:
|
||||
self._merge(srcRow, dstRow)
|
||||
|
||||
def _merge(self, src, dst):
|
||||
dstDrone = self.get(dst)
|
||||
if isinstance(dstDrone, es_Drone):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if sFit.mergeDrones(fitID, self.get(src), dstDrone, True):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedFitCommand(
|
||||
fitID=activeFit, projectedFitID=fitID, amount=1))
|
||||
|
||||
@staticmethod
|
||||
def moduleSort(module):
|
||||
return module.item.name
|
||||
return not module.isExclusiveSystemEffect, module.item.name
|
||||
|
||||
@staticmethod
|
||||
def fighterSort(fighter):
|
||||
@@ -188,7 +163,7 @@ class ProjectedView(d.Display):
|
||||
def fitChanged(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(event.fitID)
|
||||
# pyfalog.debug("ProjectedView::fitChanged: {}", repr(fit))
|
||||
# pyfalog.debug('ProjectedView::fitChanged: {}', repr(fit))
|
||||
|
||||
self.Parent.Parent.DisablePage(self, not fit or fit.isStructure)
|
||||
|
||||
@@ -201,21 +176,24 @@ class ProjectedView(d.Display):
|
||||
|
||||
stuff = []
|
||||
if fit is not None:
|
||||
# pyfalog.debug(" Collecting list of stuff to display in ProjectedView")
|
||||
self.modules = fit.projectedModules[:]
|
||||
self.drones = fit.projectedDrones[:]
|
||||
self.fighters = fit.projectedFighters[:]
|
||||
self.originalFits = fit.projectedFits
|
||||
self.fits = fit.projectedFits[:]
|
||||
self.originalModules = fit.projectedModules
|
||||
self.modules = fit.projectedModules[:]
|
||||
self.originalDrones = fit.projectedDrones
|
||||
self.drones = fit.projectedDrones[:]
|
||||
self.originalFighters = fit.projectedFighters
|
||||
self.fighters = fit.projectedFighters[:]
|
||||
|
||||
self.fits.sort(key=self.fitSort)
|
||||
self.modules.sort(key=self.moduleSort)
|
||||
self.drones.sort(key=self.droneSort)
|
||||
self.fighters.sort(key=self.fighterSort)
|
||||
self.fits.sort(key=self.fitSort)
|
||||
|
||||
stuff.extend(self.fits)
|
||||
stuff.extend(self.modules)
|
||||
stuff.extend(self.drones)
|
||||
stuff.extend(self.fighters)
|
||||
stuff.extend(self.fits)
|
||||
|
||||
if event.fitID != self.lastFitId:
|
||||
self.lastFitId = event.fitID
|
||||
@@ -225,10 +203,10 @@ class ProjectedView(d.Display):
|
||||
if item != -1:
|
||||
self.EnsureVisible(item)
|
||||
|
||||
self.deselectItems()
|
||||
self.unselectAll()
|
||||
|
||||
if not stuff:
|
||||
stuff = [DummyEntry("Drag an item or fit, or use right-click menu for wormhole effects")]
|
||||
stuff = [DummyEntry('Drag an item or fit, or use right-click menu for wormhole effects')]
|
||||
|
||||
self.update(stuff)
|
||||
|
||||
@@ -238,90 +216,149 @@ class ProjectedView(d.Display):
|
||||
if row == -1:
|
||||
return None
|
||||
|
||||
numFits = len(self.fits)
|
||||
numMods = len(self.modules)
|
||||
numDrones = len(self.drones)
|
||||
numFighters = len(self.fighters)
|
||||
numFits = len(self.fits)
|
||||
|
||||
if (numMods + numDrones + numFighters + numFits) == 0:
|
||||
if (numFits + numMods + numDrones + numFighters) == 0:
|
||||
return None
|
||||
|
||||
if row < numMods:
|
||||
stuff = self.modules[row]
|
||||
elif row - numMods < numDrones:
|
||||
stuff = self.drones[row - numMods]
|
||||
elif row - numMods - numDrones < numFighters:
|
||||
stuff = self.fighters[row - numMods - numDrones]
|
||||
if row < numFits:
|
||||
fit = self.fits[row]
|
||||
if fit in self.originalFits:
|
||||
return fit
|
||||
elif row - numFits < numMods:
|
||||
mod = self.modules[row - numFits]
|
||||
if mod in self.originalModules:
|
||||
return mod
|
||||
elif row - numFits - numMods < numDrones:
|
||||
drone = self.drones[row - numFits - numMods]
|
||||
if drone in self.originalDrones:
|
||||
return drone
|
||||
else:
|
||||
stuff = self.fits[row - numMods - numDrones - numFighters]
|
||||
|
||||
return stuff
|
||||
fighter = self.fighters[row - numFits - numMods - numDrones]
|
||||
if fighter in self.originalFighters:
|
||||
return fighter
|
||||
return None
|
||||
|
||||
def click(self, event):
|
||||
event.Skip()
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
item = self.get(row)
|
||||
mainRow, _ = self.HitTest(event.Position)
|
||||
if mainRow != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col == self.getColIndex(State):
|
||||
mainItem = self.get(mainRow)
|
||||
if mainItem is None:
|
||||
return
|
||||
selection = self.getSelectedProjectors()
|
||||
if mainItem not in selection:
|
||||
selection = [mainItem]
|
||||
modPressed = wx.GetMouseState().GetModifiers() == wx.MOD_ALT
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.toggleProjected(fitID, item, "right" if event.Button == 3 else "left")
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def scheduleMenu(self, event):
|
||||
if isinstance(mainItem, EosModule) and modPressed:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
selection = [fit.projectedModules[p] for p in positions]
|
||||
elif isinstance(mainItem, EosFighter) and modPressed:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
selection = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedItemStatesCommand(
|
||||
fitID=fitID,
|
||||
mainItem=mainItem,
|
||||
items=selection,
|
||||
click='right' if event.GetButton() == 3 else 'left'))
|
||||
return
|
||||
event.Skip()
|
||||
if self.getColumn(event.Position) != self.getColIndex(State):
|
||||
wx.CallAfter(self.spawnMenu)
|
||||
|
||||
def spawnMenu(self):
|
||||
def spawnMenu(self, event):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return
|
||||
|
||||
sel = self.GetFirstSelected()
|
||||
context = ()
|
||||
item = self.get(sel)
|
||||
if self.getColumn(self.screenToClientFixed(event.Position)) == self.getColIndex(State):
|
||||
return
|
||||
|
||||
if item is not None:
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainItem = self.get(clickedPos)
|
||||
|
||||
contexts = []
|
||||
if mainItem is not None:
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
if isinstance(item, es_Drone):
|
||||
srcContext = "projectedDrone"
|
||||
itemContext = sMkt.getCategoryByItem(item.item).name
|
||||
context = ((srcContext, itemContext),)
|
||||
elif isinstance(item, es_Fighter):
|
||||
srcContext = "projectedFighter"
|
||||
itemContext = sMkt.getCategoryByItem(item.item).name
|
||||
context = ((srcContext, itemContext),)
|
||||
elif isinstance(item, es_Module):
|
||||
modSrcContext = "projectedModule"
|
||||
modItemContext = sMkt.getCategoryByItem(item.item).name
|
||||
if isinstance(mainItem, EosModule):
|
||||
modSrcContext = 'projectedModule'
|
||||
modItemContext = 'Projected Item'
|
||||
modFullContext = (modSrcContext, modItemContext)
|
||||
if item.charge is not None:
|
||||
chgSrcContext = "projectedCharge"
|
||||
chgItemContext = sMkt.getCategoryByItem(item.charge).name
|
||||
chgFullContext = (chgSrcContext, chgItemContext)
|
||||
context = (modFullContext, chgFullContext)
|
||||
else:
|
||||
context = (modFullContext,)
|
||||
contexts.append(modFullContext)
|
||||
if mainItem.charge is not None:
|
||||
chargeSrcContext = 'projectedCharge'
|
||||
chargeItemContext = sMkt.getCategoryByItem(mainItem.charge).name
|
||||
chargeFullContext = (chargeSrcContext, chargeItemContext)
|
||||
contexts.append(chargeFullContext)
|
||||
elif isinstance(mainItem, EosDrone):
|
||||
srcContext = 'projectedDrone'
|
||||
itemContext = 'Projected Item'
|
||||
droneFullContext = (srcContext, itemContext)
|
||||
contexts.append(droneFullContext)
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
srcContext = 'projectedFighter'
|
||||
itemContext = 'Projected Item'
|
||||
fighterFullContext = (srcContext, itemContext)
|
||||
contexts.append(fighterFullContext)
|
||||
else:
|
||||
fitSrcContext = "projectedFit"
|
||||
fitItemContext = item.name
|
||||
context = ((fitSrcContext, fitItemContext),)
|
||||
|
||||
context += (("projected",),)
|
||||
menu = ContextMenu.getMenu((item,) if item is not None else [], *context)
|
||||
fitSrcContext = 'projectedFit'
|
||||
fitItemContext = 'Projected Item'
|
||||
fitFullContext = (fitSrcContext, fitItemContext)
|
||||
contexts.append(fitFullContext)
|
||||
contexts.append(('projected',))
|
||||
|
||||
selection = self.getSelectedProjectors()
|
||||
menu = ContextMenu.getMenu(mainItem, selection, *contexts)
|
||||
if menu is not None:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def remove(self, event):
|
||||
def onLeftDoubleClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
if row != -1:
|
||||
col = self.getColumn(event.Position)
|
||||
if col != self.getColIndex(State):
|
||||
mainItem = self.get(row)
|
||||
if mainItem is None:
|
||||
return
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
thing = self.get(row)
|
||||
if thing: # thing doesn't exist if it's the dummy value
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedCommand(fitID, thing))
|
||||
modPressed = wx.GetMouseState().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))
|
||||
elif isinstance(mainItem, EosModule):
|
||||
if modPressed:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
else:
|
||||
items = [mainItem]
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=items, amount=1))
|
||||
elif isinstance(mainItem, EosDrone):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=[mainItem], amount=math.inf if modPressed else 1))
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
if modPressed:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
else:
|
||||
items = [mainItem]
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=items, amount=1))
|
||||
else:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=[mainItem], amount=math.inf if modPressed else 1))
|
||||
|
||||
def getSelectedProjectors(self):
|
||||
projectors = []
|
||||
for row in self.getSelectedRows():
|
||||
projector = self.get(row)
|
||||
if projector is None:
|
||||
continue
|
||||
projectors.append(projector)
|
||||
return projectors
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# noinspection PyUnresolvedReferences
|
||||
from gui.builtinContextMenus import ( # noqa: E402,F401
|
||||
# Various command and projected-related items which we want to have first,
|
||||
# before generic commands
|
||||
fitOpenNewTab,
|
||||
envEffectAdd,
|
||||
fitAddCurrentlyOpen,
|
||||
commandFitAdd,
|
||||
# Often-used item manipulations
|
||||
shipModeChange,
|
||||
moduleAmmoChange,
|
||||
moduleSpool,
|
||||
boosterSideEffects,
|
||||
fighterAbilities,
|
||||
# Item info
|
||||
itemStats,
|
||||
itemMarketJump,
|
||||
fitSystemSecurity, # Not really an item info but want to keep it here
|
||||
shipJump,
|
||||
# Generic item manipulations
|
||||
itemRemove,
|
||||
itemAmountChange,
|
||||
droneSplitStack,
|
||||
itemVariationChange,
|
||||
moduleMutations,
|
||||
moduleFill,
|
||||
skillAffectors,
|
||||
# Market stuff
|
||||
itemFill,
|
||||
droneAddStack,
|
||||
cargoAdd,
|
||||
cargoAddAmmo,
|
||||
itemProject,
|
||||
ammoToDmgPattern,
|
||||
implantSetAdd,
|
||||
# Price
|
||||
priceOptions,
|
||||
# Resistance panel
|
||||
damagePatternChange,
|
||||
# Firepower panel
|
||||
factorReload,
|
||||
targetResists,
|
||||
)
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class AmmoPattern(ContextMenu):
|
||||
class AmmoToDmgPattern(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
def display(self, srcContext, mainItem):
|
||||
if not self.settings.get('ammoPattern'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
item = selection[0]
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
for attr in ("emDamage", "thermalDamage", "explosiveDamage", "kineticDamage"):
|
||||
if item.getAttribute(attr) is not None:
|
||||
if mainItem.getAttribute(attr) is not None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Set {0} as Damage Pattern".format(itmContext if itmContext is not None else "Item")
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Set {} as Damage Pattern".format(itmContext if itmContext is not None else "Item")
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
item = selection[0]
|
||||
fit = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
sFit.setAsPattern(fit, item)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit))
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
Fit.getInstance().setAsPattern(fitID, mainItem)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def getBitmap(self, context, selection):
|
||||
def getBitmap(self, context, mainItem):
|
||||
return None
|
||||
|
||||
|
||||
AmmoPattern.register()
|
||||
AmmoToDmgPattern.register()
|
||||
@@ -1,117 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
from eos.saveddata.fit import Fit as es_Fit
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import re
|
||||
from service.fit import Fit
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.cargo import Cargo as es_Cargo
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from service.settings import ContextMenuSettings
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class ChangeAmount(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('amount'):
|
||||
return False
|
||||
|
||||
return srcContext in ("droneItem", "projectedDrone", "cargoItem", "projectedFit", "fighterItem", "projectedFighter")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return u"Change {0} Quantity".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
thing = selection[0]
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
fitID = mainFrame.getActiveFit()
|
||||
srcContext = fullContext[0]
|
||||
if isinstance(thing, es_Fit):
|
||||
value = thing.getProjectionInfo(fitID).amount
|
||||
else:
|
||||
value = thing.amount
|
||||
|
||||
dlg = AmountChanger(self.mainFrame, value)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
if dlg.input.GetLineText(0).strip() == '':
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
|
||||
|
||||
if isinstance(thing, es_Cargo):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeCargoQty(fitID, fit.cargo.index(thing), int(float(cleanInput))))
|
||||
return # no need for post event here
|
||||
elif isinstance(thing, Drone):
|
||||
if srcContext == "droneItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeDroneQty(fitID, fit.drones.index(thing), int(float(cleanInput))))
|
||||
else:
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedDroneQty(fitID, fit.projectedDrones.index(thing), int(float(cleanInput))))
|
||||
elif isinstance(thing, es_Fit):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedFitQty(fitID, thing.ID, int(float(cleanInput))))
|
||||
return
|
||||
elif isinstance(thing, es_Fighter):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeFighterQty(fitID, fit.fighters.index(thing), int(float(cleanInput))))
|
||||
return
|
||||
|
||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
ChangeAmount.register()
|
||||
|
||||
|
||||
class AmountChanger(wx.Dialog):
|
||||
def __init__(self, parent, value):
|
||||
wx.Dialog.__init__(self, parent, title="Change Amount")
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, "New Amount:")
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
self.input.SetValue(str(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
|
||||
@@ -1,25 +1,27 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.contextMenu import ContextMenu
|
||||
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
from gui import fitCommands as cmd
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class BoosterSideEffect(ContextMenu):
|
||||
class BoosterSideEffects(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
# if not self.settings.get('fighterAbilities'):
|
||||
# return False
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in "boosterItem":
|
||||
return False
|
||||
|
||||
self.booster = selection[0]
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
self.booster = mainItem
|
||||
|
||||
for effect in self.booster.sideEffects:
|
||||
if effect.effect.isImplemented:
|
||||
@@ -27,19 +29,19 @@ class BoosterSideEffect(ContextMenu):
|
||||
|
||||
return False
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Side Effects"
|
||||
|
||||
def addEffect(self, menu, ability):
|
||||
label = ability.name
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.effectIds[id] = ability
|
||||
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, mainItem, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.effectIds = {}
|
||||
@@ -57,14 +59,17 @@ class BoosterSideEffect(ContextMenu):
|
||||
|
||||
def handleMode(self, event):
|
||||
effect = self.effectIds[event.Id]
|
||||
if effect is False or effect not in self.booster.sideEffects:
|
||||
booster = self.booster
|
||||
if effect is False or effect not in booster.sideEffects:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit.toggleBoosterSideEffect(fitID, effect)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if booster in fit.boosters:
|
||||
index = fit.boosters.index(booster)
|
||||
self.mainFrame.command.Submit(cmd.GuiToggleBoosterSideEffectStateCommand(
|
||||
fitID=fitID, position=index, effectID=effect.effectID))
|
||||
|
||||
|
||||
BoosterSideEffect.register()
|
||||
BoosterSideEffects.register()
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
# noinspection PyPackageRequirements
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class Cargo(ContextMenu):
|
||||
class AddToCargo(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('cargo'):
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc"):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc"):
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
@@ -27,16 +27,15 @@ class Cargo(ContextMenu):
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Add {0} to Cargo".format(itmContext)
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Add {} to Cargo".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
typeID = int(selection[0].ID)
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(fitID, typeID))
|
||||
self.mainFrame.additionsPane.select("Cargo")
|
||||
typeID = int(mainItem.ID)
|
||||
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=1)
|
||||
if self.mainFrame.command.Submit(command):
|
||||
self.mainFrame.additionsPane.select("Cargo", focus=False)
|
||||
|
||||
|
||||
Cargo.register()
|
||||
AddToCargo.register()
|
||||
36
gui/builtinContextMenus/cargoAddAmmo.py
Normal file
36
gui/builtinContextMenus/cargoAddAmmo.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class AddToCargoAmmo(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
if mainItem.category.ID != 8:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Add {0} to Cargo (x1000)".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
typeID = int(mainItem.ID)
|
||||
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=1000)
|
||||
if self.mainFrame.command.Submit(command):
|
||||
self.mainFrame.additionsPane.select("Cargo", focus=False)
|
||||
|
||||
|
||||
AddToCargoAmmo.register()
|
||||
@@ -1,35 +0,0 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class CargoAmmo(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('cargoAmmo'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
for selected_item in selection:
|
||||
if selected_item.category.ID in (
|
||||
8, # Charge
|
||||
):
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Add {0} to Cargo (x1000)".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
typeID = int(selection[0].ID)
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCargoCommand(fitID, typeID, 1000))
|
||||
self.mainFrame.additionsPane.select("Cargo")
|
||||
|
||||
|
||||
CargoAmmo.register()
|
||||
@@ -3,13 +3,14 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class CommandFits(ContextMenu):
|
||||
class AddCommandFit(ContextMenuUnconditional):
|
||||
|
||||
# Get list of items that define a command fit
|
||||
sMkt = Market.getInstance()
|
||||
grp = sMkt.getGroup(1770) # Command burst group
|
||||
@@ -41,24 +42,24 @@ class CommandFits(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
def display(self, srcContext):
|
||||
if self.mainFrame.getActiveFit() is None or len(self.__class__.commandFits) == 0 or srcContext != "commandView":
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Command Fits"
|
||||
|
||||
def addFit(self, menu, fit, includeShip=False):
|
||||
label = fit.name if not includeShip else "({}) {}".format(fit.ship.item.name, fit.name)
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.fitMenuItemIds[id] = fit
|
||||
menuItem = wx.MenuItem(menu, id, label)
|
||||
menu.Bind(wx.EVT_MENU, self.handleSelection, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.fitMenuItemIds = {}
|
||||
@@ -79,7 +80,7 @@ class CommandFits(ContextMenu):
|
||||
typeDict[shipName].append(fit)
|
||||
|
||||
for ship in sorted(typeDict.keys()):
|
||||
shipItem = wx.MenuItem(sub, ContextMenu.nextID(), ship)
|
||||
shipItem = wx.MenuItem(sub, ContextMenuUnconditional.nextID(), ship)
|
||||
grandSub = wx.Menu()
|
||||
shipItem.SetSubMenu(grandSub)
|
||||
|
||||
@@ -98,8 +99,8 @@ class CommandFits(ContextMenu):
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandCommand(fitID, fit.ID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitCommand(fitID=fitID, commandFitID=fit.ID))
|
||||
|
||||
|
||||
CommandFits.populateFits(None)
|
||||
CommandFits.register()
|
||||
AddCommandFit.populateFits(None)
|
||||
AddCommandFit.register()
|
||||
@@ -1,28 +1,31 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.fit import Fit
|
||||
from service.damagePattern import DamagePattern as import_DamagePattern
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.damagePattern import DamagePattern as import_DamagePattern
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeDamagePattern(ContextMenuUnconditional):
|
||||
|
||||
class DamagePattern(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('damagePattern'):
|
||||
return False
|
||||
def display(self, srcContext):
|
||||
return srcContext == "resistancesViewFull"
|
||||
|
||||
return srcContext == "resistancesViewFull" and self.mainFrame.getActiveFit() is not None
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.mainFrame.getActiveFit() is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
sDP = import_DamagePattern.getInstance()
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
@@ -53,7 +56,7 @@ class DamagePattern(ContextMenu):
|
||||
return self.m
|
||||
|
||||
def addPattern(self, rootMenu, pattern):
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
@@ -66,15 +69,15 @@ class DamagePattern(ContextMenu):
|
||||
# determine active pattern
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
f = sFit.getFit(fitID)
|
||||
dp = f.damagePattern
|
||||
|
||||
if dp == pattern:
|
||||
bitmap = BitmapLoader.getBitmap("state_active_small", "gui")
|
||||
menuItem.SetBitmap(bitmap)
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit:
|
||||
dp = fit.damagePattern
|
||||
if dp == pattern:
|
||||
bitmap = BitmapLoader.getBitmap("state_active_small", "gui")
|
||||
menuItem.SetBitmap(bitmap)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
|
||||
if self.m[i] not in self.subMenus:
|
||||
@@ -84,9 +87,9 @@ class DamagePattern(ContextMenu):
|
||||
id = pitem.GetId()
|
||||
self.patternIds[id] = self.singles[i]
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
|
||||
if self.patternIds[id] == self.fit.damagePattern:
|
||||
bitmap = BitmapLoader.getBitmap("state_active_small", "gui")
|
||||
pitem.SetBitmap(bitmap)
|
||||
if self.fit and self.patternIds[id] == self.fit.damagePattern:
|
||||
bitmap = BitmapLoader.getBitmap("state_active_small", "gui")
|
||||
pitem.SetBitmap(bitmap)
|
||||
return False
|
||||
|
||||
sub = wx.Menu()
|
||||
@@ -110,4 +113,4 @@ class DamagePattern(ContextMenu):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
DamagePattern.register()
|
||||
ChangeDamagePattern.register()
|
||||
50
gui/builtinContextMenus/droneAddStack.py
Normal file
50
gui/builtinContextMenus/droneAddStack.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from gui.fitCommands.helpers import droneStackLimit
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class DroneAddStack(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in ('marketItemGroup', 'marketItemMisc'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
if mainItem.category.name != 'Drone':
|
||||
return False
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
amount = droneStackLimit(fit, mainItem)
|
||||
if amount < 1:
|
||||
return False
|
||||
|
||||
self.amount = amount
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return 'Add {} to Drone Bay{}'.format(
|
||||
itmContext, '' if self.amount == 1 else ' (x{})'.format(self.amount))
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
command = cmd.GuiAddLocalDroneCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
itemID=int(mainItem.ID),
|
||||
amount=self.amount)
|
||||
if self.mainFrame.command.Submit(command):
|
||||
self.mainFrame.additionsPane.select('Drones', focus=False)
|
||||
|
||||
|
||||
DroneAddStack.register()
|
||||
@@ -1,32 +0,0 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
# noinspection PyPackageRequirements
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ItemRemove(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('droneRemoveStack'):
|
||||
return False
|
||||
|
||||
return srcContext == "droneItem"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Remove {0} Stack".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
idx = fit.drones.index(selection[0])
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveDroneCommand(fitID, idx, fit.drones[idx].amount))
|
||||
|
||||
|
||||
ItemRemove.register()
|
||||
@@ -1,145 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
from service.fit import Fit
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from service.settings import ContextMenuSettings
|
||||
import re
|
||||
|
||||
|
||||
class DroneSplit(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('droneSplit'):
|
||||
return False
|
||||
|
||||
return srcContext in ("droneItem", "projectedDrone") and selection[0].amount > 1
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Split {0} Stack".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
srcContext = fullContext[0]
|
||||
drone = selection[0]
|
||||
dlg = DroneStackSplit(self.mainFrame, drone.amount)
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
if dlg.input.GetLineText(0).strip() == '':
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
if srcContext == "droneItem":
|
||||
sFit.splitDroneStack(fitID, drone, int(float(cleanInput)))
|
||||
else:
|
||||
sFit.splitProjectedDroneStack(fitID, drone, int(float(cleanInput)))
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
# if isinstance(thing, es_Cargo):
|
||||
# self.mainFrame.command.Submit(
|
||||
# cmd.GuiAddCargoCommand(fitID, thing.item.ID, int(float(cleanInput)), replace=True))
|
||||
# return # no need for post event here
|
||||
# elif isinstance(thing, es_Fit):
|
||||
# sFit.changeAmount(fitID, thing, int(float(cleanInput)))
|
||||
# elif isinstance(thing, es_Fighter):
|
||||
# sFit.changeActiveFighters(fitID, thing, int(float(cleanInput)))
|
||||
#
|
||||
# wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
#
|
||||
# dlg = DroneSpinner(self.mainFrame, selection[0], srcContext)
|
||||
# dlg.ShowModal()
|
||||
# dlg.Destroy()
|
||||
|
||||
|
||||
DroneSplit.register()
|
||||
|
||||
|
||||
class DroneStackSplit(wx.Dialog):
|
||||
def __init__(self, parent, value):
|
||||
wx.Dialog.__init__(self, parent, title="Split Drone Stack")
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, "New Amount:")
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
self.input.SetValue(str(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
|
||||
|
||||
|
||||
class DroneSpinner(wx.Dialog):
|
||||
def __init__(self, parent, drone, context):
|
||||
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
|
||||
self.drone = drone
|
||||
self.context = context
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
self.spinner = wx.SpinCtrl(self)
|
||||
self.spinner.SetRange(1, drone.amount - 1)
|
||||
self.spinner.SetValue(1)
|
||||
|
||||
bSizer1.Add(self.spinner, 1, wx.ALL, 5)
|
||||
|
||||
self.button = wx.Button(self, wx.ID_OK, "Split")
|
||||
bSizer1.Add(self.button, 0, wx.ALL, 5)
|
||||
|
||||
self.SetSizer(bSizer1)
|
||||
self.Layout()
|
||||
self.Centre(wx.BOTH)
|
||||
self.button.Bind(wx.EVT_BUTTON, self.split)
|
||||
|
||||
def split(self, event):
|
||||
sFit = Fit.getInstance()
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
fitID = mainFrame.getActiveFit()
|
||||
if self.context == "droneItem":
|
||||
sFit.splitDroneStack(fitID, self.drone, self.spinner.GetValue())
|
||||
else:
|
||||
sFit.splitProjectedDroneStack(fitID, self.drone, self.spinner.GetValue())
|
||||
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
|
||||
event.Skip()
|
||||
99
gui/builtinContextMenus/droneSplitStack.py
Normal file
99
gui/builtinContextMenus/droneSplitStack.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class DroneSplitStack(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext != "droneItem":
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
return mainItem.amount > 1
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Split {} Stack".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
dlg = DroneStackSplit(self.mainFrame, mainItem.amount)
|
||||
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
if dlg.input.GetLineText(0).strip() == '':
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
|
||||
|
||||
if mainItem in fit.drones:
|
||||
position = fit.drones.index(mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiSplitLocalDroneStackCommand(
|
||||
fitID=fitID, position=position, amount=int(cleanInput)))
|
||||
|
||||
|
||||
DroneSplitStack.register()
|
||||
|
||||
|
||||
class DroneStackSplit(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, value):
|
||||
wx.Dialog.__init__(self, parent, title="Split Drone Stack")
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, "New Amount:")
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
self.input.SetValue(str(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
|
||||
@@ -1,42 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
import wx
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class DroneStack(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('droneStack'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
for selected_item in selection:
|
||||
if selected_item.category.ID in (
|
||||
18, # Drones
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Add {0} to Drone Bay (x5)".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
typeID = int(selection[0].ID)
|
||||
sFit.addDrone(fitID, typeID, 5)
|
||||
self.mainFrame.additionsPane.select("Drones")
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
DroneStack.register()
|
||||
@@ -6,12 +6,12 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class WhProjector(ContextMenu):
|
||||
class AddEnvironmentEffect(ContextMenuUnconditional):
|
||||
|
||||
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
|
||||
# (which can be random when going into Abyssal space). This is how we currently define it:
|
||||
@@ -28,16 +28,13 @@ class WhProjector(ContextMenu):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('whProjector'):
|
||||
return False
|
||||
|
||||
def display(self, srcContext):
|
||||
return srcContext == "projected"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Add Environmental Effect"
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
|
||||
# Wormholes
|
||||
@@ -89,13 +86,13 @@ class WhProjector(ContextMenu):
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, swObj.ID, 'item'))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID, swObj.ID))
|
||||
|
||||
def buildMenu(self, grouped_data, flat_data, local_menu, rootMenu, msw):
|
||||
|
||||
def processFlat(data, root, sub):
|
||||
for swData in sorted(data, key=lambda tpl: tpl[2]):
|
||||
wxid = ContextMenu.nextID()
|
||||
wxid = ContextMenuUnconditional.nextID()
|
||||
swObj, swName, swClass = swData
|
||||
self.idmap[wxid] = (swObj, swName)
|
||||
subItem = wx.MenuItem(sub, wxid, swClass)
|
||||
@@ -126,7 +123,8 @@ class WhProjector(ContextMenu):
|
||||
|
||||
# Expressions for matching when detecting effects we're looking for
|
||||
if incursions:
|
||||
validgroups = ("Incursion ship attributes effects",)
|
||||
validgroups = ("Incursion ship attributes effects",
|
||||
"Invasion Effects")
|
||||
else:
|
||||
validgroups = ("Black Hole Effect Beacon",
|
||||
"Cataclysmic Variable Effect Beacon",
|
||||
@@ -136,7 +134,7 @@ class WhProjector(ContextMenu):
|
||||
"Wolf Rayet Effect Beacon")
|
||||
|
||||
# Stuff we don't want to see in names
|
||||
garbages = ("Effect", "Beacon", "ship attributes effects")
|
||||
garbages = ("Effects?", "Beacon", "ship attributes effects")
|
||||
|
||||
# Get group with all the system-wide beacons
|
||||
grp = sMkt.getGroup("Effect Beacon")
|
||||
@@ -223,4 +221,4 @@ class WhProjector(ContextMenu):
|
||||
return grouped, ()
|
||||
|
||||
|
||||
WhProjector.register()
|
||||
AddEnvironmentEffect.register()
|
||||
@@ -1,28 +1,30 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FactorReload(ContextMenu):
|
||||
class FactorReload(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('factorReload'):
|
||||
return False
|
||||
def display(self, srcContext):
|
||||
return srcContext == "firepowerViewFull"
|
||||
|
||||
return srcContext == "firepowerViewFull" and self.mainFrame.getActiveFit() is not None
|
||||
@property
|
||||
def enabled(self):
|
||||
return self.mainFrame.getActiveFit() is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Factor in Reload Time"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def activate(self, fullContext, i):
|
||||
sFit = Fit.getInstance()
|
||||
sFit.serviceFittingOptions["useGlobalForceReload"] = not sFit.serviceFittingOptions["useGlobalForceReload"]
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
@@ -34,4 +36,5 @@ class FactorReload(ContextMenu):
|
||||
sFit = Fit.getInstance()
|
||||
return sFit.serviceFittingOptions["useGlobalForceReload"]
|
||||
|
||||
|
||||
FactorReload.register()
|
||||
|
||||
@@ -1,39 +1,45 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.contextMenu import ContextMenu
|
||||
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
from gui import fitCommands as cmd
|
||||
from gui.fitCommands.helpers import getSimilarFighters
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FighterAbility(ContextMenu):
|
||||
class FighterAbilities(ContextMenuCombined):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.isProjected = None
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('fighterAbilities'):
|
||||
return False
|
||||
|
||||
def display(self, srcContext, mainItem, selection):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ("fighterItem", "projectedFighter"):
|
||||
return False
|
||||
|
||||
self.fighter = selection[0]
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
self.fighter = mainItem
|
||||
self.selection = selection
|
||||
self.isProjected = True if srcContext == "projectedFighter" else False
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem, selection):
|
||||
return "Abilities"
|
||||
|
||||
def addAbility(self, menu, ability):
|
||||
label = ability.name
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuCombined.nextID()
|
||||
self.abilityIds[id] = ability
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, mainItem, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.abilityIds = {}
|
||||
@@ -55,10 +61,29 @@ class FighterAbility(ContextMenu):
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit.toggleFighterAbility(fitID, ability)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if self.isProjected:
|
||||
container = fit.projectedFighters
|
||||
command = cmd.GuiToggleProjectedFighterAbilityStateCommand
|
||||
else:
|
||||
container = fit.fighters
|
||||
command = cmd.GuiToggleLocalFighterAbilityStateCommand
|
||||
if self.fighter in container:
|
||||
mainPosition = container.index(self.fighter)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(container, self.fighter)
|
||||
else:
|
||||
fighters = self.selection
|
||||
positions = []
|
||||
for fighter in fighters:
|
||||
if fighter in container:
|
||||
positions.append(container.index(fighter))
|
||||
self.mainFrame.command.Submit(command(
|
||||
fitID=fitID,
|
||||
mainPosition=mainPosition,
|
||||
positions=positions,
|
||||
effectID=ability.effectID))
|
||||
|
||||
|
||||
FighterAbility.register()
|
||||
FighterAbilities.register()
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import gui.globalEvents as GE
|
||||
from service.settings import ContextMenuSettings
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class FillWithModule(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('moduleFill'):
|
||||
return False
|
||||
return srcContext in ("fittingModule")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return u"Fill With {0}".format(itmContext if itmContext is not None else "Module")
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
|
||||
srcContext = fullContext[0]
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
if srcContext == "fittingModule":
|
||||
self.mainFrame.command.Submit(cmd.GuiFillWithModuleCommand(fitID, selection[0].itemID))
|
||||
return # the command takes care of the PostEvent
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
FillWithModule.register()
|
||||
@@ -1,30 +1,29 @@
|
||||
# coding: utf-8
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.builtinViews.emptyView import BlankPage
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class TabbedFits(ContextMenu):
|
||||
class AddCurrentlyOpenFit(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
def display(self, srcContext):
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ("projected", "commandView"):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ('projected', 'commandView'):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Currently Open Fits"
|
||||
def getText(self, itmContext):
|
||||
return 'Add Currently Open Fit'
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
self.fitLookup = {}
|
||||
self.context = context
|
||||
sFit = Fit.getInstance()
|
||||
@@ -42,7 +41,7 @@ class TabbedFits(ContextMenu):
|
||||
if isinstance(page, BlankPage):
|
||||
continue
|
||||
fit = sFit.getFit(page.activeFitID, basic=True)
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name))
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||
self.fitLookup[id] = fit
|
||||
@@ -56,9 +55,9 @@ class TabbedFits(ContextMenu):
|
||||
fit = self.fitLookup[event.Id]
|
||||
|
||||
if self.context == 'commandView':
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandCommand(fitID, fit.ID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddCommandFitCommand(fitID=fitID, commandFitID=fit.ID))
|
||||
elif self.context == 'projected':
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, fit.ID, 'fit'))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedFitCommand(fitID=fitID, projectedFitID=fit.ID, amount=1))
|
||||
|
||||
|
||||
TabbedFits.register()
|
||||
AddCurrentlyOpenFit.register()
|
||||
36
gui/builtinContextMenus/fitOpenNewTab.py
Normal file
36
gui/builtinContextMenus/fitOpenNewTab.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.builtinShipBrowser.events import FitSelected
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class OpenFitInNewTab(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in ("projectedFit", "commandFit"):
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
currentFitID = self.mainFrame.getActiveFit()
|
||||
selectedFitID = mainItem.ID
|
||||
if currentFitID == selectedFitID:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Open Fit in New Tab"
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=mainItem.ID, startup=2))
|
||||
|
||||
|
||||
OpenFitInNewTab.register()
|
||||
67
gui/builtinContextMenus/fitSystemSecurity.py
Normal file
67
gui/builtinContextMenus/fitSystemSecurity.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.const import FitSystemSecurity
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
optionMap = OrderedDict((
|
||||
('High Security', FitSystemSecurity.HISEC),
|
||||
('Low Security', FitSystemSecurity.LOWSEC),
|
||||
('Null Security', FitSystemSecurity.NULLSEC),
|
||||
('W-Space', FitSystemSecurity.WSPACE)))
|
||||
|
||||
|
||||
class FitSystemSecurityMenu(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def display(self, srcContext):
|
||||
if srcContext != "fittingShip":
|
||||
return False
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
|
||||
if not fit.isStructure:
|
||||
return
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext):
|
||||
return "Citadel System Security"
|
||||
|
||||
def addOption(self, menu, optionLabel):
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.optionIds[id] = optionLabel
|
||||
menuItem = wx.MenuItem(menu, id, optionLabel, kind=wx.ITEM_CHECK)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.optionIds = {}
|
||||
sub = wx.Menu()
|
||||
for optionLabel, optionValue in optionMap.items():
|
||||
menuItem = self.addOption(rootMenu if msw else sub, optionLabel)
|
||||
sub.Append(menuItem)
|
||||
menuItem.Check(fit.getSystemSecurity() == optionValue)
|
||||
|
||||
return sub
|
||||
|
||||
def handleMode(self, event):
|
||||
optionLabel = self.optionIds[event.Id]
|
||||
optionValue = optionMap[optionLabel]
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeFitSystemSecurityCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
secStatus=optionValue))
|
||||
|
||||
|
||||
FitSystemSecurityMenu.register()
|
||||
@@ -1,42 +1,41 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from service.implantSet import ImplantSets as s_ImplantSets
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.character import Character
|
||||
from service.fit import Fit
|
||||
from service.implantSet import ImplantSets as s_ImplantSets
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ImplantSets(ContextMenu):
|
||||
class AddImplantSet(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('implantSets'):
|
||||
return False
|
||||
def display(self, srcContext, mainItem):
|
||||
|
||||
sIS = s_ImplantSets.getInstance()
|
||||
implantSets = sIS.getImplantSetList()
|
||||
|
||||
if len(implantSets) == 0:
|
||||
return False
|
||||
|
||||
return srcContext in ("implantView", "implantEditor")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Add Implant Set"
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, mainItem, rootMenu, i, pitem):
|
||||
"""
|
||||
A note on the selection here: Most context menus act on a fit, so it's easy enough to get the active fit from
|
||||
A note on the mainItem here: Most context menus act on a fit, so it's easy enough to get the active fit from
|
||||
the MainFrame instance. There's never been a reason to get info from another window, so there's not common
|
||||
way of doing this. However, we use this context menu within the Character Editor to apply implant sets to a
|
||||
character, so we need to access the character editor.
|
||||
|
||||
It is for these reasons that I hijack the selection parameter when calling the menu and pass a pointer to the
|
||||
It is for these reasons that I hijack the mainItem parameter when calling the menu and pass a pointer to the
|
||||
Character Editor. This way we can use it to get current editing character ID and apply the implants.
|
||||
|
||||
It would probably be better to have a function on the MainFrame to get the currently open Character Editor (as
|
||||
@@ -51,13 +50,12 @@ class ImplantSets(ContextMenu):
|
||||
implantSets = sIS.getImplantSetList()
|
||||
|
||||
self.context = context
|
||||
if len(selection) == 1:
|
||||
self.selection = selection[0] # dirty hack here
|
||||
self.mainItem = mainItem # dirty hack here
|
||||
|
||||
self.idmap = {}
|
||||
|
||||
for set in implantSets:
|
||||
id = ContextMenu.nextID()
|
||||
for set in sorted(implantSets, key=lambda i: i.name):
|
||||
id = ContextMenuSingle.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, set.name)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
|
||||
self.idmap[id] = set
|
||||
@@ -75,19 +73,16 @@ class ImplantSets(ContextMenu):
|
||||
if self.context == "implantEditor":
|
||||
# we are calling from character editor, the implant source is different
|
||||
sChar = Character.getInstance()
|
||||
char = self.selection.entityEditor.getActiveEntity()
|
||||
char = self.mainItem.entityEditor.getActiveEntity()
|
||||
|
||||
for implant in set.implants:
|
||||
sChar.addImplant(char.ID, implant.item.ID)
|
||||
|
||||
wx.PostEvent(self.selection, GE.CharChanged())
|
||||
wx.PostEvent(self.mainItem, GE.CharChanged())
|
||||
else:
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
for implant in set.implants:
|
||||
sFit.addImplant(fitID, implant.item.ID, recalc=implant == set.implants[-1])
|
||||
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
self.mainFrame.command.Submit(cmd.GuiAddImplantSetCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
itemIDs=[i.itemID for i in set.implants]))
|
||||
|
||||
|
||||
ImplantSets.register()
|
||||
AddImplantSet.register()
|
||||
136
gui/builtinContextMenus/itemAmountChange.py
Normal file
136
gui/builtinContextMenus/itemAmountChange.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import re
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.cargo import Cargo as es_Cargo
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter as es_Fighter
|
||||
from eos.saveddata.fit import Fit as es_Fit
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeItemAmount(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in ("droneItem", "projectedDrone", "cargoItem", "projectedFit", "fighterItem", "projectedFighter"):
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Change {0} Quantity".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
srcContext = fullContext[0]
|
||||
if isinstance(mainItem, es_Fit):
|
||||
try:
|
||||
value = mainItem.getProjectionInfo(fitID).amount
|
||||
except AttributeError:
|
||||
return
|
||||
elif isinstance(mainItem, es_Fighter):
|
||||
value = mainItem.amountActive
|
||||
else:
|
||||
value = mainItem.amount
|
||||
|
||||
dlg = AmountChanger(self.mainFrame, value, (0, 20)) if isinstance(mainItem, es_Fit) else AmountChanger(self.mainFrame, value)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
|
||||
if dlg.input.GetLineText(0).strip() == '':
|
||||
return
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
cleanInput = int(float(re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())))
|
||||
|
||||
if isinstance(mainItem, es_Cargo):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeCargoAmountCommand(
|
||||
fitID=fitID, itemID=mainItem.itemID, amount=cleanInput))
|
||||
elif isinstance(mainItem, Drone):
|
||||
if srcContext == "projectedDrone":
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedDroneAmountCommand(
|
||||
fitID=fitID, itemID=mainItem.itemID, amount=cleanInput))
|
||||
else:
|
||||
if mainItem in fit.drones:
|
||||
position = fit.drones.index(mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalDroneAmountCommand(
|
||||
fitID=fitID, position=position, amount=cleanInput))
|
||||
elif isinstance(mainItem, es_Fit):
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedFitAmountCommand(
|
||||
fitID=fitID, projectedFitID=mainItem.ID, amount=cleanInput))
|
||||
elif isinstance(mainItem, es_Fighter):
|
||||
if srcContext == "projectedFighter":
|
||||
if mainItem in fit.projectedFighters:
|
||||
position = fit.projectedFighters.index(mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedFighterAmountCommand(
|
||||
fitID=fitID, position=position, amount=cleanInput))
|
||||
else:
|
||||
if mainItem in fit.fighters:
|
||||
position = fit.fighters.index(mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalFighterAmountCommand(
|
||||
fitID=fitID, position=position, amount=cleanInput))
|
||||
|
||||
|
||||
ChangeItemAmount.register()
|
||||
|
||||
|
||||
class AmountChanger(wx.Dialog):
|
||||
|
||||
def __init__(self, parent, value, limits=None):
|
||||
wx.Dialog.__init__(self, parent, title="Change Amount")
|
||||
self.SetMinSize((346, 156))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
bSizer2 = wx.BoxSizer(wx.VERTICAL)
|
||||
text = wx.StaticText(self, wx.ID_ANY, "New Amount:" if limits is None else "New Amount ({}-{})".format(*limits))
|
||||
bSizer2.Add(text, 0)
|
||||
|
||||
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
|
||||
|
||||
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
|
||||
self.input.SetValue(str(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
|
||||
40
gui/builtinContextMenus/itemFill.py
Normal file
40
gui/builtinContextMenus/itemFill.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FillWithItem(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if not self.settings.get('moduleFill'):
|
||||
return False
|
||||
|
||||
if srcContext not in ('marketItemGroup', 'marketItemMisc'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
if mainItem.category.name != 'Module':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Fill With Module"
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(
|
||||
fitID=self.mainFrame.getActiveFit(),
|
||||
itemID=int(mainItem.ID)))
|
||||
|
||||
|
||||
FillWithItem.register()
|
||||
@@ -1,60 +1,60 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class MarketJump(ContextMenu):
|
||||
class JumpToMarketItem(ContextMenuSingle):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('marketJump'):
|
||||
return False
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
validContexts = ("marketItemMisc", "fittingModule",
|
||||
"fittingCharge", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"projectedModule", "projectedDrone",
|
||||
"projectedCharge", "cargoItem",
|
||||
"implantItemChar", "fighterItem",
|
||||
"projectedDrone")
|
||||
"projectedFighter")
|
||||
|
||||
if srcContext not in validContexts or selection is None or len(selection) < 1:
|
||||
if srcContext not in validContexts or mainItem is None:
|
||||
return False
|
||||
|
||||
if mainItem is None or getattr(mainItem, "isEmpty", False):
|
||||
return False
|
||||
|
||||
sMkt = Market.getInstance()
|
||||
item = getattr(selection[0], "item", selection[0])
|
||||
isMutated = getattr(selection[0], "isMutated", False)
|
||||
item = getattr(mainItem, "item", mainItem)
|
||||
isMutated = getattr(mainItem, "isMutated", False)
|
||||
mktGrp = sMkt.getMarketGroupByItem(item)
|
||||
if mktGrp is None and isMutated:
|
||||
mktGrp = sMkt.getMarketGroupByItem(selection[0].baseItem)
|
||||
mktGrp = sMkt.getMarketGroupByItem(mainItem.baseItem)
|
||||
|
||||
# 1663 is Special Edition Festival Assets, we don't have root group for it
|
||||
if mktGrp is None or mktGrp.ID == 1663:
|
||||
return False
|
||||
|
||||
doit = not selection[0].isEmpty if srcContext == "fittingModule" else True
|
||||
doit = not mainItem.isEmpty if srcContext == "fittingModule" else True
|
||||
return doit
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "{0} Market Group".format(itmContext if itmContext is not None else "Item")
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
srcContext = fullContext[0]
|
||||
if srcContext in ("fittingCharge", "projectedCharge"):
|
||||
item = selection[0].charge
|
||||
elif hasattr(selection[0], "item"):
|
||||
if getattr(selection[0], "isMutated", False):
|
||||
item = selection[0].baseItem
|
||||
item = mainItem.charge
|
||||
elif hasattr(mainItem, "item"):
|
||||
if getattr(mainItem, "isMutated", False):
|
||||
item = mainItem.baseItem
|
||||
else:
|
||||
item = selection[0].item
|
||||
item = mainItem.item
|
||||
else:
|
||||
item = selection[0]
|
||||
item = mainItem
|
||||
|
||||
self.mainFrame.notebookBrowsers.SetSelection(0)
|
||||
self.mainFrame.marketBrowser.jump(item)
|
||||
|
||||
|
||||
MarketJump.register()
|
||||
JumpToMarketItem.register()
|
||||
50
gui/builtinContextMenus/itemProject.py
Normal file
50
gui/builtinContextMenus/itemProject.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ProjectItem(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if not self.settings.get('project'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None:
|
||||
return False
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
if fit.isStructure:
|
||||
return False
|
||||
|
||||
return mainItem.isType("projected")
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Project {0} onto Fit".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if mainItem.isModule:
|
||||
success = self.mainFrame.command.Submit(cmd.GuiAddProjectedModuleCommand(fitID=fitID, itemID=mainItem.ID))
|
||||
elif mainItem.isDrone:
|
||||
success = self.mainFrame.command.Submit(cmd.GuiAddProjectedDroneCommand(fitID=fitID, itemID=mainItem.ID))
|
||||
elif mainItem.isFighter:
|
||||
success = self.mainFrame.command.Submit(cmd.GuiAddProjectedFighterCommand(fitID=fitID, itemID=mainItem.ID))
|
||||
else:
|
||||
success = False
|
||||
if success:
|
||||
self.mainFrame.additionsPane.select('Projected', focus=False)
|
||||
|
||||
|
||||
ProjectItem.register()
|
||||
@@ -1,69 +1,163 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import math
|
||||
|
||||
import wx
|
||||
import gui.globalEvents as GE
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.drone import Drone as EosDrone
|
||||
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
|
||||
from service.settings import ContextMenuSettings
|
||||
import gui.fitCommands as cmd
|
||||
|
||||
|
||||
class ItemRemove(ContextMenu):
|
||||
class RemoveItem(ContextMenuCombined):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('itemRemove'):
|
||||
def display(self, srcContext, mainItem, selection):
|
||||
if srcContext not in (
|
||||
"fittingModule", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"projectedModule", "cargoItem",
|
||||
"projectedFit", "projectedDrone",
|
||||
"fighterItem", "projectedFighter",
|
||||
"commandFit"
|
||||
):
|
||||
return False
|
||||
|
||||
return srcContext in ("fittingModule", "fittingCharge",
|
||||
"droneItem", "implantItem",
|
||||
"boosterItem", "projectedModule",
|
||||
"projectedCharge", "cargoItem",
|
||||
"projectedFit", "projectedDrone",
|
||||
"fighterItem", "projectedFighter",
|
||||
"commandFit")
|
||||
if mainItem is None or getattr(mainItem, "isEmpty", False):
|
||||
return False
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return u"Remove {0}".format(itmContext if itmContext is not None else "Item")
|
||||
self.srcContext = srcContext
|
||||
return True
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def getText(self, itmContext, mainItem, selection):
|
||||
return 'Remove {}{}'.format(
|
||||
itmContext if itmContext is not None else 'Item',
|
||||
' Stack' if self.srcContext in ('droneItem', 'projectedDrone', 'cargoItem', 'projectedFit') else '')
|
||||
|
||||
def activate(self, fullContext, mainItem, selection, i):
|
||||
handlerMap = {
|
||||
'fittingModule': self.__handleModule,
|
||||
'droneItem': self.__handleDrone,
|
||||
'fighterItem': self.__handleFighter,
|
||||
'implantItem': self.__handleImplant,
|
||||
'boosterItem': self.__handleBooster,
|
||||
'cargoItem': self.__handleCargo,
|
||||
'projectedFit': self.__handleProjectedItem,
|
||||
'projectedModule': self.__handleProjectedItem,
|
||||
'projectedDrone': self.__handleProjectedItem,
|
||||
'projectedFighter': self.__handleProjectedItem,
|
||||
'commandFit': self.__handleCommandFit}
|
||||
srcContext = fullContext[0]
|
||||
sFit = Fit.getInstance()
|
||||
handler = handlerMap.get(srcContext)
|
||||
if handler is None:
|
||||
return
|
||||
handler(mainItem, selection)
|
||||
|
||||
def __handleModule(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
positions = getSimilarModPositions(fit.modules, mainItem)
|
||||
else:
|
||||
positions = []
|
||||
for mod in selection:
|
||||
if mod in fit.modules:
|
||||
positions.append(fit.modules.index(mod))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalModuleCommand(
|
||||
fitID=fitID, positions=positions))
|
||||
|
||||
if srcContext == "fittingModule":
|
||||
modules = [module for module in selection if module is not None]
|
||||
self.mainFrame.command.Submit(cmd.GuiModuleRemoveCommand(fitID, modules))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext in ("fittingCharge", "projectedCharge"):
|
||||
self.mainFrame.command.Submit(cmd.GuiModuleAddChargeCommand(fitID, None, selection))
|
||||
return
|
||||
elif srcContext == "droneItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveDroneCommand(fitID, fit.drones.index(selection[0])))
|
||||
return
|
||||
elif srcContext == "fighterItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveFighterCommand(fitID, fit.fighters.index(selection[0])))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext == "implantItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantCommand(fitID, fit.implants.index(selection[0])))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext == "boosterItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveBoosterCommand(fitID, fit.boosters.index(selection[0])))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext == "cargoItem":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCargoCommand(fitID, selection[0].itemID))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext in ("projectedFit", "projectedModule", "projectedDrone", "projectedFighter"):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedCommand(fitID, selection[0]))
|
||||
return # the command takes care of the PostEvent
|
||||
elif srcContext == "commandFit":
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCommandCommand(fitID, selection[0].ID))
|
||||
return # the command takes care of the PostEvent
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
def __handleDrone(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = []
|
||||
for drone in selection:
|
||||
if drone in fit.drones:
|
||||
positions.append(fit.drones.index(drone))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalDronesCommand(
|
||||
fitID=fitID, positions=positions, amount=math.inf))
|
||||
|
||||
def __handleFighter(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(fit.fighters, mainItem)
|
||||
else:
|
||||
fighters = selection
|
||||
positions = []
|
||||
for fighter in fighters:
|
||||
if fighter in fit.fighters:
|
||||
positions.append(fit.fighters.index(fighter))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveLocalFightersCommand(
|
||||
fitID=fitID, positions=positions))
|
||||
|
||||
def __handleImplant(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = []
|
||||
for implant in selection:
|
||||
if implant in fit.implants:
|
||||
positions.append(fit.implants.index(implant))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveImplantsCommand(
|
||||
fitID=fitID, positions=positions))
|
||||
|
||||
def __handleBooster(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = []
|
||||
for booster in selection:
|
||||
if booster in fit.boosters:
|
||||
positions.append(fit.boosters.index(booster))
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveBoostersCommand(
|
||||
fitID=fitID, positions=positions))
|
||||
|
||||
def __handleCargo(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
itemIDs = [c.itemID for c in selection]
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCargosCommand(
|
||||
fitID=fitID, itemIDs=itemIDs))
|
||||
|
||||
def __handleProjectedItem(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if isinstance(mainItem, EosFit):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosModule):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
positions = getSimilarModPositions(fit.projectedModules, mainItem)
|
||||
items = [fit.projectedModules[p] for p in positions]
|
||||
else:
|
||||
items = selection
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=items, amount=math.inf))
|
||||
elif isinstance(mainItem, EosDrone):
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
elif isinstance(mainItem, EosFighter):
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
items = getSimilarFighters(fit.projectedFighters, mainItem)
|
||||
else:
|
||||
items = selection
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=items, amount=math.inf))
|
||||
else:
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveProjectedItemsCommand(
|
||||
fitID=fitID, items=selection, amount=math.inf))
|
||||
|
||||
def __handleCommandFit(self, mainItem, selection):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
commandFitIDs = [cf.ID for cf in selection]
|
||||
self.mainFrame.command.Submit(cmd.GuiRemoveCommandFitsCommand(
|
||||
fitID=fitID, commandFitIDs=commandFitIDs))
|
||||
|
||||
|
||||
ItemRemove.register()
|
||||
RemoveItem.register()
|
||||
|
||||
@@ -1,45 +1,52 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.itemStats import ItemStatsDialog
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from gui.itemStats import ItemStatsDialog
|
||||
from service.fit import Fit
|
||||
from eos.saveddata.mode import Mode
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ItemStats(ContextMenu):
|
||||
class ItemStats(ContextMenuSingle):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('itemStats'):
|
||||
def display(self, srcContext, mainItem):
|
||||
if srcContext not in (
|
||||
"marketItemGroup", "marketItemMisc",
|
||||
"fittingModule", "fittingCharge",
|
||||
"fittingShip", "baseShip",
|
||||
"cargoItem", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"skillItem", "projectedModule",
|
||||
"projectedDrone", "projectedCharge",
|
||||
"itemStats", "fighterItem",
|
||||
"implantItemChar", "projectedFighter",
|
||||
"fittingMode"
|
||||
):
|
||||
return False
|
||||
|
||||
return srcContext in ("marketItemGroup", "marketItemMisc",
|
||||
"fittingModule", "fittingCharge",
|
||||
"fittingShip", "baseShip",
|
||||
"cargoItem", "droneItem",
|
||||
"implantItem", "boosterItem",
|
||||
"skillItem", "projectedModule",
|
||||
"projectedDrone", "projectedCharge",
|
||||
"itemStats", "fighterItem",
|
||||
"implantItemChar", "projectedFighter",
|
||||
"fittingMode")
|
||||
if (mainItem is None or getattr(mainItem, "isEmpty", False)) and srcContext != "fittingShip":
|
||||
return False
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "{0} Stats".format(itmContext if itmContext is not None else "Item")
|
||||
return True
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "{} Stats".format(itmContext if itmContext is not None else "Item")
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
srcContext = fullContext[0]
|
||||
if srcContext == "fittingShip":
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
stuff = sFit.getFit(fitID).ship
|
||||
elif srcContext == "fittingMode":
|
||||
stuff = selection[0].item
|
||||
stuff = mainItem.item
|
||||
else:
|
||||
stuff = selection[0]
|
||||
stuff = mainItem
|
||||
|
||||
if srcContext == "fittingModule" and stuff.isEmpty:
|
||||
return
|
||||
@@ -47,7 +54,7 @@ class ItemStats(ContextMenu):
|
||||
mstate = wx.GetMouseState()
|
||||
reuse = False
|
||||
|
||||
if mstate.cmdDown:
|
||||
if mstate.GetModifiers() == wx.MOD_SHIFT:
|
||||
reuse = True
|
||||
|
||||
if self.mainFrame.GetActiveStatsWindow() is None and reuse:
|
||||
|
||||
298
gui/builtinContextMenus/itemVariationChange.py
Normal file
298
gui/builtinContextMenus/itemVariationChange.py
Normal file
@@ -0,0 +1,298 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions, getSimilarFighters
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeItemToVariation(ContextMenuCombined):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem, selection):
|
||||
if not self.settings.get('metaSwap'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in (
|
||||
'fittingModule',
|
||||
'droneItem',
|
||||
'fighterItem',
|
||||
'boosterItem',
|
||||
'implantItem',
|
||||
'cargoItem',
|
||||
'projectedModule',
|
||||
'projectedDrone',
|
||||
'projectedFighter'
|
||||
):
|
||||
return False
|
||||
|
||||
if mainItem is None or getattr(mainItem, 'isEmpty', False):
|
||||
return False
|
||||
|
||||
self.mainVariations = Market.getInstance().getVariationsByItems((mainItem.item,))
|
||||
# No variations from current module
|
||||
if len(self.mainVariations) < 2:
|
||||
return False
|
||||
|
||||
self.mainItem = mainItem
|
||||
self.selection = selection
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem, selection):
|
||||
return 'Variations'
|
||||
|
||||
def getSubMenu(self, context, mainItem, selection, rootMenu, i, pitem):
|
||||
self.moduleLookup = {}
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
def get_metalevel(x):
|
||||
if 'metaLevel' not in x.attributes:
|
||||
return 0
|
||||
return x.attributes['metaLevel'].value
|
||||
|
||||
def get_metagroup(x):
|
||||
# We want deadspace before officer mods
|
||||
remap = {5: 6, 6: 5}
|
||||
return remap.get(x.metaGroup.ID, x.metaGroup.ID) if x.metaGroup is not None else 0
|
||||
|
||||
def get_boosterrank(x):
|
||||
# If we're returning a lot of items, sort my name
|
||||
if len(self.mainVariations) > 7:
|
||||
return x.name
|
||||
# Sort by booster chance to get some sort of pseudorank.
|
||||
elif 'boosterEffectChance1' in x.attributes:
|
||||
return x.attributes['boosterEffectChance1'].value
|
||||
# the "first" rank (Synth) doesn't have boosterEffectChance1. If we're not pulling back all boosters, return 0 for proper sorting
|
||||
else:
|
||||
return 0
|
||||
|
||||
m = wx.Menu()
|
||||
|
||||
# If on Windows we need to bind out events into the root menu, on other
|
||||
# platforms they need to go to our sub menu
|
||||
if 'wxMSW' in wx.PlatformInfo:
|
||||
bindmenu = rootMenu
|
||||
else:
|
||||
bindmenu = m
|
||||
|
||||
# Sort items by metalevel, and group within that metalevel
|
||||
items = list(self.mainVariations)
|
||||
# Sort all items by name first
|
||||
items.sort(key=lambda x: x.name)
|
||||
# Do not do any extra sorting for implants
|
||||
if 'implantItem' in context:
|
||||
pass
|
||||
# Boosters don't have meta or anything concrete that we can rank by. Go by chance to inflict side effect
|
||||
elif 'boosterItem' in context:
|
||||
items.sort(key=get_boosterrank)
|
||||
else:
|
||||
# sort by group and meta level
|
||||
items.sort(key=get_metalevel)
|
||||
items.sort(key=get_metagroup)
|
||||
|
||||
group = None
|
||||
for item in items:
|
||||
# Apparently no metaGroup for the Tech I variant:
|
||||
if 'subSystem' in item.effects:
|
||||
thisgroup = item.marketGroup.marketGroupName
|
||||
elif item.metaGroup is None:
|
||||
thisgroup = 'Tech I'
|
||||
else:
|
||||
thisgroup = item.metaGroup.name
|
||||
|
||||
if thisgroup != group and context not in ('implantItem', 'boosterItem'):
|
||||
group = thisgroup
|
||||
id = ContextMenuCombined.nextID()
|
||||
m.Append(id, '─ %s ─' % group)
|
||||
m.Enable(id, False)
|
||||
|
||||
id = ContextMenuCombined.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, item.name)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSwitch, mitem)
|
||||
|
||||
self.moduleLookup[id] = item, context
|
||||
m.Append(mitem)
|
||||
mitem.Enable(fit.canFit(item))
|
||||
|
||||
return m
|
||||
|
||||
def handleSwitch(self, event):
|
||||
item, context = self.moduleLookup.get(event.Id, None)
|
||||
if item is None:
|
||||
event.Skip()
|
||||
return
|
||||
handlerMap = {
|
||||
'fittingModule': self.__handleModule,
|
||||
'droneItem': self.__handleDrone,
|
||||
'fighterItem': self.__handleFighter,
|
||||
'cargoItem': self.__handleCargo,
|
||||
'implantItem': self.__handleImplant,
|
||||
'boosterItem': self.__handleBooster,
|
||||
'projectedModule': self.__handleProjectedModule,
|
||||
'projectedDrone': self.__handleProjectedDrone,
|
||||
'projectedFighter': self.__handleProjectedFighter}
|
||||
handler = handlerMap.get(context)
|
||||
if handler is None:
|
||||
return
|
||||
handler(item)
|
||||
|
||||
def __handleModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
positions = getSimilarModPositions(fit.modules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
positions = []
|
||||
for mod in self.selection:
|
||||
if mod.isEmpty:
|
||||
continue
|
||||
if mod is self.mainItem:
|
||||
positions.append(fit.modules.index(mod))
|
||||
continue
|
||||
if mod not in fit.modules:
|
||||
continue
|
||||
modVariations = sMkt.getVariationsByItems((mod.item,))
|
||||
if modVariations == self.mainVariations:
|
||||
positions.append(fit.modules.index(mod))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalModuleMetasCommand(
|
||||
fitID=fitID, positions=positions, newItemID=varItem.ID))
|
||||
|
||||
def __handleDrone(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
sMkt = Market.getInstance()
|
||||
positions = []
|
||||
for drone in self.selection:
|
||||
if drone not in fit.drones:
|
||||
continue
|
||||
if drone is self.mainItem:
|
||||
positions.append(fit.drones.index(drone))
|
||||
continue
|
||||
droneVariations = sMkt.getVariationsByItems((drone.item,))
|
||||
if droneVariations == self.mainVariations:
|
||||
positions.append(fit.drones.index(drone))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalDroneMetasCommand(
|
||||
fitID=fitID, positions=positions, newItemID=varItem.ID))
|
||||
|
||||
def __handleFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(fit.fighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
sMkt = Market.getInstance()
|
||||
positions = []
|
||||
for fighter in fighters:
|
||||
if fighter not in fit.fighters:
|
||||
continue
|
||||
if fighter is self.mainItem:
|
||||
positions.append(fit.fighters.index(fighter))
|
||||
continue
|
||||
fighterVariations = sMkt.getVariationsByItems((fighter.item,))
|
||||
if fighterVariations == self.mainVariations:
|
||||
positions.append(fit.fighters.index(fighter))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalFighterMetasCommand(
|
||||
fitID=fitID, positions=positions, newItemID=varItem.ID))
|
||||
|
||||
def __handleCargo(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sMkt = Market.getInstance()
|
||||
itemIDs = []
|
||||
for cargo in self.selection:
|
||||
if cargo is self.mainItem:
|
||||
itemIDs.append(cargo.itemID)
|
||||
continue
|
||||
cargoVariations = sMkt.getVariationsByItems((cargo.item,))
|
||||
if cargoVariations == self.mainVariations:
|
||||
itemIDs.append(cargo.itemID)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeCargoMetasCommand(
|
||||
fitID=fitID, itemIDs=itemIDs, newItemID=varItem.ID))
|
||||
|
||||
def __handleImplant(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
implant = self.mainItem
|
||||
if implant in fit.implants:
|
||||
position = fit.implants.index(implant)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeImplantMetaCommand(
|
||||
fitID=fitID, position=position, newItemID=varItem.ID))
|
||||
|
||||
def __handleBooster(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
booster = self.mainItem
|
||||
if booster in fit.boosters:
|
||||
position = fit.boosters.index(booster)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeBoosterMetaCommand(
|
||||
fitID=fitID, position=position, newItemID=varItem.ID))
|
||||
|
||||
def __handleProjectedModule(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
positions = getSimilarModPositions(fit.projectedModules, self.mainItem)
|
||||
else:
|
||||
sMkt = Market.getInstance()
|
||||
positions = []
|
||||
for mod in self.selection:
|
||||
if mod is self.mainItem:
|
||||
positions.append(fit.projectedModules.index(mod))
|
||||
continue
|
||||
if mod not in fit.projectedModules:
|
||||
continue
|
||||
modVariations = sMkt.getVariationsByItems((mod.item,))
|
||||
if modVariations == self.mainVariations:
|
||||
positions.append(fit.projectedModules.index(mod))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedModuleMetasCommand(
|
||||
fitID=fitID, positions=positions, newItemID=varItem.ID))
|
||||
|
||||
def __handleProjectedDrone(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
sMkt = Market.getInstance()
|
||||
itemIDs = []
|
||||
for drone in self.selection:
|
||||
if drone not in fit.projectedDrones:
|
||||
continue
|
||||
if drone is self.mainItem:
|
||||
itemIDs.append(drone.itemID)
|
||||
continue
|
||||
droneVariations = sMkt.getVariationsByItems((drone.item,))
|
||||
if droneVariations == self.mainVariations:
|
||||
itemIDs.append(drone.itemID)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedDroneMetasCommand(
|
||||
fitID=fitID, itemIDs=itemIDs, newItemID=varItem.ID))
|
||||
|
||||
def __handleProjectedFighter(self, varItem):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if wx.GetMouseState().GetModifiers() == wx.MOD_ALT:
|
||||
fighters = getSimilarFighters(fit.projectedFighters, self.mainItem)
|
||||
else:
|
||||
fighters = self.selection
|
||||
sMkt = Market.getInstance()
|
||||
positions = []
|
||||
for fighter in fighters:
|
||||
if fighter not in fit.projectedFighters:
|
||||
continue
|
||||
if fighter is self.mainItem:
|
||||
positions.append(fit.projectedFighters.index(fighter))
|
||||
continue
|
||||
fighterVariations = sMkt.getVariationsByItems((fighter.item,))
|
||||
if fighterVariations == self.mainVariations:
|
||||
positions.append(fit.projectedFighters.index(fighter))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedFighterMetasCommand(
|
||||
fitID=fitID, positions=positions, newItemID=varItem.ID))
|
||||
|
||||
|
||||
ChangeItemToVariation.register()
|
||||
@@ -1,154 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class MetaSwap(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('metaSwap'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in (
|
||||
"fittingModule",
|
||||
"droneItem",
|
||||
"fighterItem",
|
||||
"boosterItem",
|
||||
"implantItem",
|
||||
"cargoItem",
|
||||
):
|
||||
return False
|
||||
|
||||
# Check if list of variations is same for all of selection
|
||||
# If not - don't show the menu
|
||||
mkt = Market.getInstance()
|
||||
self.variations = None
|
||||
for i in selection:
|
||||
variations = mkt.getVariationsByItems([i.item])
|
||||
if self.variations is None:
|
||||
self.variations = variations
|
||||
else:
|
||||
if variations != self.variations:
|
||||
return False
|
||||
|
||||
self.selection = selection
|
||||
|
||||
if len(self.variations) == 1:
|
||||
return False # no variations from current module
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Variations"
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
self.moduleLookup = {}
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(self.mainFrame.getActiveFit())
|
||||
|
||||
def get_metalevel(x):
|
||||
if "metaLevel" not in x.attributes:
|
||||
return 0
|
||||
return x.attributes["metaLevel"].value
|
||||
|
||||
def get_metagroup(x):
|
||||
return x.metaGroup.ID if x.metaGroup is not None else 0
|
||||
|
||||
def get_boosterrank(x):
|
||||
# If we're returning a lot of items, sort my name
|
||||
if len(self.variations) > 7:
|
||||
return x.name
|
||||
# Sort by booster chance to get some sort of pseudorank.
|
||||
elif 'boosterEffectChance1' in x.attributes:
|
||||
return x.attributes['boosterEffectChance1'].value
|
||||
# the "first" rank (Synth) doesn't have boosterEffectChance1. If we're not pulling back all boosters, return 0 for proper sorting
|
||||
else:
|
||||
return 0
|
||||
|
||||
m = wx.Menu()
|
||||
|
||||
# If on Windows we need to bind out events into the root menu, on other
|
||||
# platforms they need to go to our sub menu
|
||||
if "wxMSW" in wx.PlatformInfo:
|
||||
bindmenu = rootMenu
|
||||
else:
|
||||
bindmenu = m
|
||||
|
||||
# Sort items by metalevel, and group within that metalevel
|
||||
items = list(self.variations)
|
||||
|
||||
if "implantItem" in context:
|
||||
# sort implants based on name
|
||||
items.sort(key=lambda x: x.name)
|
||||
elif "boosterItem" in context:
|
||||
# boosters don't have meta or anything concrete that we can rank by. Go by chance to inflict side effect
|
||||
items.sort(key=get_boosterrank)
|
||||
else:
|
||||
# sort by group and meta level
|
||||
items.sort(key=get_metalevel)
|
||||
items.sort(key=get_metagroup)
|
||||
|
||||
group = None
|
||||
for item in items:
|
||||
# Apparently no metaGroup for the Tech I variant:
|
||||
if "subSystem" in item.effects:
|
||||
thisgroup = item.marketGroup.marketGroupName
|
||||
elif item.metaGroup is None:
|
||||
thisgroup = "Tech I"
|
||||
else:
|
||||
thisgroup = item.metaGroup.name
|
||||
|
||||
if thisgroup != group and context not in ("implantItem", "boosterItem"):
|
||||
group = thisgroup
|
||||
id = ContextMenu.nextID()
|
||||
m.Append(id, '─ %s ─' % group)
|
||||
m.Enable(id, False)
|
||||
|
||||
id = ContextMenu.nextID()
|
||||
mitem = wx.MenuItem(rootMenu, id, item.name)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleModule, mitem)
|
||||
|
||||
self.moduleLookup[id] = item, context
|
||||
m.Append(mitem)
|
||||
mitem.Enable(fit.canFit(item))
|
||||
|
||||
return m
|
||||
|
||||
def handleModule(self, event):
|
||||
item, context = self.moduleLookup.get(event.Id, None)
|
||||
if item is None:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiMetaSwapCommand(fitID, context, item.ID, self.selection))
|
||||
|
||||
# for selected_item in self.selection:
|
||||
|
||||
#
|
||||
# elif isinstance(selected_item, Drone):
|
||||
# drone_count = None
|
||||
#
|
||||
# for idx, drone_stack in enumerate(fit.drones):
|
||||
# if drone_stack is selected_item:
|
||||
# drone_count = drone_stack.amount
|
||||
# sFit.removeDrone(fitID, idx, drone_count, False)
|
||||
# break
|
||||
#
|
||||
# if drone_count:
|
||||
# sFit.addDrone(fitID, item.ID, drone_count, True)
|
||||
|
||||
|
||||
MetaSwap.register()
|
||||
@@ -1,60 +1,63 @@
|
||||
# coding: utf-8
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ModuleAmmoPicker(ContextMenu):
|
||||
class ChangeModuleAmmo(ContextMenuCombined):
|
||||
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
# Format: {type ID: set(loadable, charges)}
|
||||
self.loadableCharges = {}
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('moduleAmmoPicker'):
|
||||
def display(self, srcContext, mainItem, selection):
|
||||
if srcContext not in ("fittingModule", "projectedModule"):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in ("fittingModule", "projectedModule"):
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
modules = selection if srcContext == "fittingModule" else (selection[0],)
|
||||
|
||||
validCharges = None
|
||||
checkedTypes = set()
|
||||
|
||||
for mod in modules:
|
||||
# loop through modules and gather list of valid charges
|
||||
if mod.item.ID in checkedTypes:
|
||||
continue
|
||||
checkedTypes.add(mod.item.ID)
|
||||
currCharges = mod.getValidCharges()
|
||||
if len(currCharges) > 0:
|
||||
if validCharges is not None and validCharges != currCharges:
|
||||
return False
|
||||
|
||||
validCharges = currCharges
|
||||
self.module = mod
|
||||
|
||||
if validCharges is None:
|
||||
self.mainCharges = self.getChargesForMod(mainItem)
|
||||
if not self.mainCharges:
|
||||
return False
|
||||
|
||||
self.modules = modules
|
||||
self.charges = list([charge for charge in validCharges if Market.getInstance().getPublicityByItem(charge)])
|
||||
return len(self.charges) > 0
|
||||
self.module = mainItem
|
||||
self.selection = selection
|
||||
self.srcContext = srcContext
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem, selection):
|
||||
return "Charge"
|
||||
|
||||
def getChargesForMod(self, mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
return set()
|
||||
typeID = mod.item.ID
|
||||
if typeID in self.loadableCharges:
|
||||
return self.loadableCharges[typeID]
|
||||
chargeSet = self.loadableCharges.setdefault(typeID, set())
|
||||
# Do not try to grab it for modes which can also be passed as part of selection
|
||||
if isinstance(mod, Module):
|
||||
for charge in mod.getValidCharges():
|
||||
if sMkt.getPublicityByItem(charge):
|
||||
chargeSet.add(charge)
|
||||
return chargeSet
|
||||
|
||||
def turretSorter(self, charge):
|
||||
damage = 0
|
||||
range_ = (self.module.item.getAttribute("maxRange")) * \
|
||||
@@ -110,7 +113,7 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
return list(map(self.numericConverter, parts))
|
||||
|
||||
def addCharge(self, menu, charge):
|
||||
id_ = ContextMenu.nextID()
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
name = charge.name if charge is not None else "Empty"
|
||||
self.chargeIds[id_] = charge
|
||||
item = wx.MenuItem(menu, id_, name)
|
||||
@@ -125,11 +128,11 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
|
||||
@staticmethod
|
||||
def addSeperator(m, text):
|
||||
id_ = ContextMenu.nextID()
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, mainItem, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
m = wx.Menu()
|
||||
self.chargeIds = {}
|
||||
@@ -142,14 +145,9 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
range_ = None
|
||||
nameBase = None
|
||||
sub = None
|
||||
self.charges.sort(key=self.turretSorter)
|
||||
for charge in self.charges:
|
||||
# fix issue 71 - will probably have to change if CCP adds more Orbital ammo
|
||||
if "Orbital" in charge.name:
|
||||
# uncomment if we ever want to include Oribital ammo in ammo picker - see issue #71
|
||||
# This allows us to hide the ammo, but it's still loadable from the market
|
||||
# item = self.addCharge(m, charge)
|
||||
# items.append(item)
|
||||
chargesSorted = sorted(self.mainCharges, key=self.turretSorter)
|
||||
for charge in chargesSorted:
|
||||
if "civilian" in charge.name.lower():
|
||||
continue
|
||||
currBase = charge.name.rsplit()[-2:]
|
||||
currRange = charge.getAttribute("weaponRangeMultiplier")
|
||||
@@ -181,11 +179,11 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
|
||||
self.addSeperator(m, "Short Range")
|
||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
self.charges.sort(key=self.missileSorter)
|
||||
type_ = None
|
||||
sub = None
|
||||
defender = None
|
||||
for charge in self.charges:
|
||||
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
|
||||
for charge in chargesSorted:
|
||||
currType = self.damageInfo(charge)[0]
|
||||
|
||||
if currType != type_ or type_ is None:
|
||||
@@ -214,8 +212,8 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
else:
|
||||
self.charges.sort(key=self.nameSorter)
|
||||
for charge in self.charges:
|
||||
chargesSorted = sorted(self.mainCharges, key=self.nameSorter)
|
||||
for charge in chargesSorted:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, charge))
|
||||
|
||||
m.Append(self.addCharge(rootMenu if msw else m, None))
|
||||
@@ -228,7 +226,44 @@ class ModuleAmmoPicker(ContextMenu):
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiModuleAddChargeCommand(fitID, charge.ID if charge is not None else None, self.modules))
|
||||
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)
|
||||
if switchAll:
|
||||
if self.srcContext == 'fittingModule':
|
||||
command = cmd.GuiChangeLocalModuleChargesCommand
|
||||
modContainer = fit.modules
|
||||
elif self.srcContext == 'projectedModule':
|
||||
command = cmd.GuiChangeProjectedModuleChargesCommand
|
||||
modContainer = fit.projectedModules
|
||||
else:
|
||||
return
|
||||
positions = getSimilarModPositions(modContainer, self.module)
|
||||
self.mainFrame.command.Submit(command(
|
||||
fitID=fitID,
|
||||
positions=positions,
|
||||
chargeItemID=charge.ID if charge is not None else None))
|
||||
else:
|
||||
if self.srcContext == 'fittingModule':
|
||||
command = cmd.GuiChangeLocalModuleChargesCommand
|
||||
modContainer = fit.modules
|
||||
elif self.srcContext == 'projectedModule':
|
||||
command = cmd.GuiChangeProjectedModuleChargesCommand
|
||||
modContainer = fit.projectedModules
|
||||
else:
|
||||
return
|
||||
positions = []
|
||||
for position, mod in enumerate(modContainer):
|
||||
if mod in self.selection:
|
||||
modCharges = self.getChargesForMod(mod)
|
||||
if modCharges.issubset(self.mainCharges):
|
||||
positions.append(position)
|
||||
self.mainFrame.command.Submit(command(
|
||||
fitID=fitID,
|
||||
positions=positions,
|
||||
chargeItemID=charge.ID if charge is not None else None))
|
||||
|
||||
|
||||
ModuleAmmoPicker.register()
|
||||
ChangeModuleAmmo.register()
|
||||
40
gui/builtinContextMenus/moduleFill.py
Normal file
40
gui/builtinContextMenus/moduleFill.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class FillWithModule(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
|
||||
if not self.settings.get('moduleFill'):
|
||||
return False
|
||||
|
||||
if mainItem is None or getattr(mainItem, 'isEmpty', False):
|
||||
return False
|
||||
|
||||
return srcContext == "fittingModule"
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Fill With {0}".format(itmContext if itmContext is not None else "Module")
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
|
||||
srcContext = fullContext[0]
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
if srcContext == "fittingModule":
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if mainItem in fit.modules:
|
||||
position = fit.modules.index(mainItem)
|
||||
self.mainFrame.command.Submit(cmd.GuiFillWithClonedLocalModulesCommand(
|
||||
fitID=fitID, position=position))
|
||||
|
||||
|
||||
FillWithModule.register()
|
||||
@@ -1,58 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.db.saveddata.queries import getFit as db_getFit
|
||||
# noinspection PyPackageRequirements
|
||||
from gui.builtinContextMenus.moduleAmmoPicker import ModuleAmmoPicker
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ModuleGlobalAmmoPicker(ModuleAmmoPicker):
|
||||
def __init__(self):
|
||||
super(ModuleGlobalAmmoPicker, self).__init__()
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Charge (All)"
|
||||
|
||||
def handleAmmoSwitch(self, event):
|
||||
if len(self.modules) != 1:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
charge = self.chargeIds.get(event.Id, False)
|
||||
if charge is False:
|
||||
event.Skip()
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = db_getFit(fitID)
|
||||
|
||||
selectedModule = self.modules[0]
|
||||
source = fit.modules if not selectedModule.isProjected else fit.projectedModules
|
||||
allModules = []
|
||||
for mod in source:
|
||||
if mod.itemID is None:
|
||||
continue
|
||||
if mod.itemID == selectedModule.itemID:
|
||||
allModules.append(mod)
|
||||
|
||||
self.mainFrame.command.Submit(cmd.GuiModuleAddChargeCommand(fitID, charge.ID if charge is not None else None, allModules))
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('moduleGlobalAmmoPicker'):
|
||||
return False
|
||||
|
||||
try:
|
||||
selectionLen = len(selection)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if selectionLen != 1:
|
||||
return False
|
||||
|
||||
return super(ModuleGlobalAmmoPicker, self).display(srcContext, selection)
|
||||
|
||||
|
||||
ModuleGlobalAmmoPicker.register()
|
||||
75
gui/builtinContextMenus/moduleMutations.py
Normal file
75
gui/builtinContextMenus/moduleMutations.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from gui.fitCommands import GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeModuleMutation(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.eventIDs = {}
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
|
||||
if srcContext != "fittingModule" or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None or mainItem.isEmpty:
|
||||
return False
|
||||
|
||||
if len(mainItem.item.mutaplasmids) == 0 and not mainItem.isMutated:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Apply Mutaplasmid" if not mainItem.isMutated else "Revert to {}".format(mainItem.baseItem.name)
|
||||
|
||||
def getSubMenu(self, context, mainItem, rootMenu, i, pitem):
|
||||
if mainItem.isMutated:
|
||||
return None
|
||||
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.skillIds = {}
|
||||
sub = wx.Menu()
|
||||
|
||||
menu = rootMenu if msw else sub
|
||||
|
||||
for item in mainItem.item.mutaplasmids:
|
||||
label = item.item.name
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.eventIDs[id] = (item, mainItem)
|
||||
skillItem = wx.MenuItem(menu, id, label)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMenu, skillItem)
|
||||
sub.Append(skillItem)
|
||||
|
||||
return sub
|
||||
|
||||
def handleMenu(self, event):
|
||||
mutaplasmid, mod = self.eventIDs[event.Id]
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if mod in fit.modules:
|
||||
position = fit.modules.index(mod)
|
||||
self.mainFrame.command.Submit(GuiConvertMutatedLocalModuleCommand(
|
||||
fitID=fitID, position=position, mutaplasmid=mutaplasmid))
|
||||
|
||||
def activate(self, fullContext, mainItem, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if mainItem in fit.modules:
|
||||
position = fit.modules.index(mainItem)
|
||||
self.mainFrame.command.Submit(GuiRevertMutatedLocalModuleCommand(
|
||||
fitID=fitID, position=position))
|
||||
|
||||
def getBitmap(self, context, mainItem):
|
||||
return None
|
||||
|
||||
|
||||
ChangeModuleMutation.register()
|
||||
128
gui/builtinContextMenus/moduleSpool.py
Normal file
128
gui/builtinContextMenus/moduleSpool.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import math
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import eos.config
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeModuleSpool(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.cycleMap = {}
|
||||
self.resetId = None
|
||||
|
||||
def display(self, srcContext, mainItem):
|
||||
if not self.settings.get('spoolup'):
|
||||
return False
|
||||
|
||||
if srcContext not in ('fittingModule', 'projectedModule') or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
if mainItem is None or mainItem.isEmpty:
|
||||
return False
|
||||
|
||||
self.mod = mainItem
|
||||
self.context = srcContext
|
||||
|
||||
return self.mod.item.group.name in ("Precursor Weapon", "Mutadaptive Remote Armor Repairer")
|
||||
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Spoolup Cycles"
|
||||
|
||||
def getSubMenu(self, context, mainItem, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
if "wxMSW" in wx.PlatformInfo:
|
||||
bindmenu = rootMenu
|
||||
else:
|
||||
bindmenu = m
|
||||
|
||||
isNotDefault = self.mod.spoolType is not None and self.mod.spoolAmount is not None
|
||||
cycleDefault = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], True))[0]
|
||||
cycleCurrent = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], False))[0]
|
||||
cycleMin = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True))[0]
|
||||
cycleMax = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True))[0]
|
||||
cycleTotalMin = min(cycleDefault, cycleCurrent, cycleMin)
|
||||
cycleTotalMax = max(cycleDefault, cycleCurrent, cycleMax)
|
||||
|
||||
def findCycles(val1, val2):
|
||||
# Try to compose list of 21 steps max (0-20)
|
||||
maxSteps = 20
|
||||
valDiff = val2 - val1
|
||||
valScale = valDiff / maxSteps
|
||||
minStep = math.ceil(round(valScale, 9))
|
||||
maxStep = math.floor(round(valDiff / 4, 9))
|
||||
# Check steps from smallest to highest and see if we can go from min value
|
||||
# to max value using those
|
||||
for currentStep in range(minStep, maxStep + 1):
|
||||
if valDiff % currentStep == 0:
|
||||
return set(range(val1, val2 + currentStep, currentStep))
|
||||
# Otherwise just split range in halves and go both ends using min values
|
||||
else:
|
||||
cycles = set()
|
||||
while val2 >= val1:
|
||||
cycles.add(val1)
|
||||
cycles.add(val2)
|
||||
val1 += minStep
|
||||
val2 -= minStep
|
||||
return cycles
|
||||
|
||||
cyclesToShow = findCycles(cycleMin, cycleMax)
|
||||
for cycle in range(cycleTotalMin, cycleTotalMax + 1):
|
||||
menuId = ContextMenuSingle.nextID()
|
||||
|
||||
# Show default only for current value and when not overriden
|
||||
if not isNotDefault and cycle == cycleDefault:
|
||||
text = "{} (default)".format(cycle)
|
||||
# Always show current selection and stuff which we decided to show via the cycles function
|
||||
elif cycle == cycleCurrent or cycle in cyclesToShow:
|
||||
text = "{}".format(cycle)
|
||||
# Ignore the rest to not have very long menu
|
||||
else:
|
||||
continue
|
||||
|
||||
item = wx.MenuItem(m, menuId, text, kind=wx.ITEM_CHECK)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
item.Check(isNotDefault and cycle == cycleCurrent)
|
||||
self.cycleMap[menuId] = cycle
|
||||
|
||||
self.resetId = ContextMenuSingle.nextID()
|
||||
item = wx.MenuItem(m, self.resetId, "Reset")
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
|
||||
return m
|
||||
|
||||
def handleSpoolChange(self, event):
|
||||
if event.Id == self.resetId:
|
||||
spoolType = None
|
||||
spoolAmount = None
|
||||
elif event.Id in self.cycleMap:
|
||||
spoolType = SpoolType.CYCLES
|
||||
spoolAmount = self.cycleMap[event.Id]
|
||||
else:
|
||||
return
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = Fit.getInstance().getFit(fitID)
|
||||
if self.context == 'fittingModule':
|
||||
if self.mod in fit.modules:
|
||||
position = fit.modules.index(self.mod)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeLocalModuleSpoolCommand(
|
||||
fitID=fitID, position=position, spoolType=spoolType, spoolAmount=spoolAmount))
|
||||
elif self.context == 'projectedModule':
|
||||
if self.mod in fit.projectedModules:
|
||||
position = fit.projectedModules.index(self.mod)
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeProjectedModuleSpoolCommand(
|
||||
fitID=fitID, position=position, spoolType=spoolType, spoolAmount=spoolAmount))
|
||||
|
||||
|
||||
ChangeModuleSpool.register()
|
||||
@@ -1,78 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import gui.globalEvents as GE
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class MutaplasmidCM(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.eventIDs = {}
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
|
||||
# if not self.settings.get('ammoPattern'):
|
||||
# return False
|
||||
|
||||
if srcContext not in "fittingModule" or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
mod = selection[0]
|
||||
if len(mod.item.mutaplasmids) == 0 and not mod.isMutated:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
mod = selection[0]
|
||||
return "Apply Mutaplasmid" if not mod.isMutated else "Revert to {}".format(mod.baseItem.name)
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
if selection[0].isMutated:
|
||||
return None
|
||||
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.skillIds = {}
|
||||
sub = wx.Menu()
|
||||
|
||||
mod = selection[0]
|
||||
|
||||
menu = rootMenu if msw else sub
|
||||
|
||||
for item in mod.item.mutaplasmids:
|
||||
label = item.item.name
|
||||
id = ContextMenu.nextID()
|
||||
self.eventIDs[id] = (item, mod)
|
||||
skillItem = wx.MenuItem(menu, id, label)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMenu, skillItem)
|
||||
sub.Append(skillItem)
|
||||
|
||||
return sub
|
||||
|
||||
def handleMenu(self, event):
|
||||
mutaplasmid, mod = self.eventIDs[event.Id]
|
||||
fit = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
|
||||
# todo: dev out function to switch module to an abyssal module. Also, maybe open item stats here automatically
|
||||
# with the attribute tab set?
|
||||
sFit.convertMutaplasmid(fit, mod.modPosition, mutaplasmid)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit))
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
|
||||
mod = selection[0]
|
||||
sFit.changeModule(fitID, mod.modPosition, mod.baseItemID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def getBitmap(self, context, selection):
|
||||
return None
|
||||
|
||||
|
||||
MutaplasmidCM.register()
|
||||
@@ -1,28 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.builtinShipBrowser.events import FitSelected
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class OpenFit(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('openFit'):
|
||||
return False
|
||||
|
||||
return srcContext in ("projectedFit", "commandFit")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Open Fit in New Tab"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
fit = selection[0]
|
||||
wx.PostEvent(self.mainFrame, FitSelected(fitID=fit.ID, startup=2))
|
||||
|
||||
|
||||
OpenFit.register()
|
||||
@@ -1,30 +0,0 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
import gui.globalEvents as GE
|
||||
from service.price import Price
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class PriceClear(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('priceClear'):
|
||||
return False
|
||||
|
||||
return srcContext in ("priceViewFull", "priceViewMinimal")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Reset Price Cache"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
sPrc = Price.getInstance()
|
||||
sPrc.clearPriceCache()
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit()))
|
||||
|
||||
|
||||
PriceClear.register()
|
||||
@@ -1,50 +1,62 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.settings import PriceMenuSettings
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.settings import MarketPriceSettings
|
||||
|
||||
|
||||
class PriceOptions(ContextMenu):
|
||||
class ItemGroupPrice(ContextMenuUnconditional, metaclass=ABCMeta):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = PriceMenuSettings.getInstance()
|
||||
self.optionList = ["Ship", "Modules", "Drones", "Cargo", "Character"]
|
||||
self.settings = MarketPriceSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
@property
|
||||
@abstractmethod
|
||||
def label(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def optionName(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def display(self, srcContext):
|
||||
return srcContext in ("priceViewFull", "priceViewMinimal")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Include in total"
|
||||
|
||||
def addOption(self, menu, option):
|
||||
label = option
|
||||
id = ContextMenu.nextID()
|
||||
self.optionIds[id] = option
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.optionIds = {}
|
||||
|
||||
sub = wx.Menu()
|
||||
|
||||
for option in self.optionList:
|
||||
menuItem = self.addOption(rootMenu if msw else sub, option)
|
||||
sub.Append(menuItem)
|
||||
menuItem.Check(self.settings.get(option.lower()))
|
||||
|
||||
return sub
|
||||
|
||||
def handleMode(self, event):
|
||||
option = self.optionIds[event.Id]
|
||||
self.settings.set(option.lower(), event.Int)
|
||||
def getText(self, itmContext):
|
||||
return self.label
|
||||
|
||||
def activate(self, fullContext, i):
|
||||
self.settings.set(self.optionName, not self.settings.get(self.optionName))
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit()))
|
||||
|
||||
@property
|
||||
def checked(self):
|
||||
return self.settings.get(self.optionName)
|
||||
|
||||
PriceOptions.register()
|
||||
|
||||
class DronesPrice(ItemGroupPrice):
|
||||
|
||||
label = 'Drones'
|
||||
optionName = 'drones'
|
||||
|
||||
|
||||
class CargoPrice(ItemGroupPrice):
|
||||
|
||||
label = 'Cargo'
|
||||
optionName = 'cargo'
|
||||
|
||||
|
||||
class ImplantBoosterPrice(ItemGroupPrice):
|
||||
|
||||
label = 'Implants && Boosters'
|
||||
optionName = 'character'
|
||||
|
||||
|
||||
DronesPrice.register()
|
||||
CargoPrice.register()
|
||||
ImplantBoosterPrice.register()
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
# noinspection PyPackageRequirements
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class Project(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('project'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
sFit = Fit.getInstance()
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
fit = sFit.getFit(fitID)
|
||||
|
||||
if fit.isStructure:
|
||||
return False
|
||||
|
||||
item = selection[0]
|
||||
return item.isType("projected")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Project {0} onto Fit".format(itmContext)
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, selection[0].ID, 'item'))
|
||||
|
||||
# trigger = sFit.project(fitID, selection[0])
|
||||
# if trigger:
|
||||
# wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
# self.mainFrame.additionsPane.select("Projected")
|
||||
|
||||
|
||||
Project.register()
|
||||
@@ -1,34 +1,43 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.contextMenu import ContextMenu
|
||||
|
||||
import gui.mainFrame
|
||||
from gui.builtinShipBrowser.events import Stage3Selected
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ShipJump(ContextMenu):
|
||||
class JumpToShip(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('shipJump'):
|
||||
def display(self, srcContext):
|
||||
if srcContext != "fittingShip":
|
||||
return False
|
||||
fitTabSelected = self.mainFrame.notebookBrowsers.GetSelection() == 1
|
||||
if not fitTabSelected:
|
||||
return True
|
||||
browsingStage = self.mainFrame.shipBrowser.GetActiveStage()
|
||||
if browsingStage != 3:
|
||||
return True
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
ship = Fit.getInstance().getFit(fitID).ship
|
||||
browsingShipID = self.mainFrame.shipBrowser.GetStageData(browsingStage)
|
||||
if browsingShipID != ship.item.ID:
|
||||
return True
|
||||
return False
|
||||
|
||||
return srcContext == "fittingShip"
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Open in Fitting Browser"
|
||||
|
||||
def activate(self, fullContext, selection, i):
|
||||
def activate(self, fullContext, i):
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
stuff = sFit.getFit(fitID).ship
|
||||
groupID = stuff.item.group.ID
|
||||
|
||||
ship = Fit.getInstance().getFit(fitID).ship
|
||||
self.mainFrame.notebookBrowsers.SetSelection(1)
|
||||
wx.PostEvent(self.mainFrame.shipBrowser, Stage3Selected(shipID=stuff.item.ID, back=groupID))
|
||||
wx.PostEvent(self.mainFrame.shipBrowser, Stage3Selected(shipID=ship.item.ID, back=True))
|
||||
|
||||
|
||||
ShipJump.register()
|
||||
JumpToShip.register()
|
||||
|
||||
@@ -3,20 +3,18 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from gui.contextMenu import ContextMenu
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class TacticalMode(ContextMenu):
|
||||
class ChangeShipTacticalMode(ContextMenuUnconditional):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('tacticalMode'):
|
||||
return False
|
||||
|
||||
def display(self, srcContext):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext != "fittingShip":
|
||||
return False
|
||||
|
||||
@@ -29,18 +27,18 @@ class TacticalMode(ContextMenu):
|
||||
|
||||
return srcContext == "fittingShip" and self.modes is not None
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Tactical Mode"
|
||||
|
||||
def addMode(self, menu, mode):
|
||||
label = mode.item.name.rsplit()[-2]
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
self.modeIds[id] = mode
|
||||
menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_RADIO)
|
||||
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.context = context
|
||||
self.modeIds = {}
|
||||
@@ -61,7 +59,7 @@ class TacticalMode(ContextMenu):
|
||||
return
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
self.mainFrame.command.Submit(cmd.GuiSetModeCommand(fitID, self.modeIds[event.Id]))
|
||||
self.mainFrame.command.Submit(cmd.GuiChangeShipModeCommand(fitID, self.modeIds[event.Id].item.ID))
|
||||
|
||||
|
||||
TacticalMode.register()
|
||||
ChangeShipTacticalMode.register()
|
||||
@@ -1,32 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from eos.saveddata.character import Skill
|
||||
|
||||
import gui.globalEvents as GE
|
||||
from service.fit import Fit
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.character import Skill
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuSingle
|
||||
from service.character import Character
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
|
||||
|
||||
class ChangeAffectingSkills(ContextMenu):
|
||||
class ChangeAffectingSkills(ContextMenuSingle):
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
def display(self, srcContext, mainItem):
|
||||
if not self.settings.get('changeAffectingSkills'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None or srcContext not in (
|
||||
"fittingModule", "fittingCharge", "fittingShip", "droneItem", "fighterItem"):
|
||||
if srcContext not in (
|
||||
"fittingModule", "fittingCharge",
|
||||
"fittingShip", "droneItem",
|
||||
"fighterItem"
|
||||
):
|
||||
return False
|
||||
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
if fitID is None:
|
||||
return False
|
||||
|
||||
if (mainItem is None or getattr(mainItem, "isEmpty", False)) and srcContext != "fittingShip":
|
||||
return False
|
||||
|
||||
self.sChar = Character.getInstance()
|
||||
self.sFit = Fit.getInstance()
|
||||
fit = self.sFit.getFit(self.mainFrame.getActiveFit())
|
||||
fit = self.sFit.getFit(fitID)
|
||||
|
||||
self.charID = fit.character.ID
|
||||
|
||||
@@ -34,14 +45,13 @@ class ChangeAffectingSkills(ContextMenu):
|
||||
# return False
|
||||
|
||||
if srcContext == "fittingShip":
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
sFit = Fit.getInstance()
|
||||
self.stuff = sFit.getFit(fitID).ship
|
||||
cont = sFit.getFit(fitID).ship.itemModifiedAttributes
|
||||
elif srcContext == "fittingCharge":
|
||||
cont = selection[0].chargeModifiedAttributes
|
||||
cont = mainItem.chargeModifiedAttributes
|
||||
else:
|
||||
cont = selection[0].itemModifiedAttributes
|
||||
cont = mainItem.itemModifiedAttributes
|
||||
|
||||
skills = set()
|
||||
|
||||
@@ -60,7 +70,7 @@ class ChangeAffectingSkills(ContextMenu):
|
||||
self.skills = sorted(skills, key=lambda x: x.item.name)
|
||||
return len(self.skills) > 0
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext, mainItem):
|
||||
return "Change %s Skills" % itmContext
|
||||
|
||||
def addSkill(self, rootMenu, skill, i):
|
||||
@@ -69,19 +79,19 @@ class ChangeAffectingSkills(ContextMenu):
|
||||
else:
|
||||
label = "Level %s" % i
|
||||
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuSingle.nextID()
|
||||
self.skillIds[id] = (skill, i)
|
||||
menuItem = wx.MenuItem(rootMenu, id, label, kind=wx.ITEM_RADIO)
|
||||
rootMenu.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
|
||||
return menuItem
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, mainItem, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.skillIds = {}
|
||||
sub = wx.Menu()
|
||||
|
||||
for skill in self.skills:
|
||||
skillItem = wx.MenuItem(sub, ContextMenu.nextID(), skill.item.name)
|
||||
skillItem = wx.MenuItem(sub, ContextMenuSingle.nextID(), skill.item.name)
|
||||
grandSub = wx.Menu()
|
||||
skillItem.SetSubMenu(grandSub)
|
||||
if skill.learned:
|
||||
@@ -1,82 +0,0 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import eos.config
|
||||
import gui.mainFrame
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions
|
||||
from gui import globalEvents as GE
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class SpoolUp(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
self.cycleMap = {}
|
||||
self.resetId = None
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('spoolup'):
|
||||
return False
|
||||
|
||||
if srcContext not in ("fittingModule") or self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
self.mod = selection[0]
|
||||
|
||||
return self.mod.item.group.name in ("Precursor Weapon", "Mutadaptive Remote Armor Repairer")
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
return "Spoolup Cycles"
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
m = wx.Menu()
|
||||
if "wxMSW" in wx.PlatformInfo:
|
||||
bindmenu = rootMenu
|
||||
else:
|
||||
bindmenu = m
|
||||
|
||||
isNotDefault = self.mod.spoolType is not None and self.mod.spoolAmount is not None
|
||||
cycleDefault = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], True))[0]
|
||||
cycleCurrent = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, eos.config.settings['globalDefaultSpoolupPercentage'], False))[0]
|
||||
cycleMin = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 0, True))[0]
|
||||
cycleMax = self.mod.getSpoolData(spoolOptions=SpoolOptions(SpoolType.SCALE, 1, True))[0]
|
||||
|
||||
for cycle in range(cycleMin, cycleMax + 1):
|
||||
menuId = ContextMenu.nextID()
|
||||
|
||||
# Show default only for current value and when not overriden
|
||||
if not isNotDefault and cycle == cycleDefault:
|
||||
text = "{} (default)".format(cycle)
|
||||
else:
|
||||
text = "{}".format(cycle)
|
||||
|
||||
item = wx.MenuItem(m, menuId, text, kind=wx.ITEM_CHECK)
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
item.Check(isNotDefault and cycle == cycleCurrent)
|
||||
self.cycleMap[menuId] = cycle
|
||||
|
||||
self.resetId = ContextMenu.nextID()
|
||||
item = wx.MenuItem(m, self.resetId, "Reset")
|
||||
bindmenu.Bind(wx.EVT_MENU, self.handleSpoolChange, item)
|
||||
m.Append(item)
|
||||
|
||||
return m
|
||||
|
||||
def handleSpoolChange(self, event):
|
||||
if event.Id == self.resetId:
|
||||
self.mod.spoolType = None
|
||||
self.mod.spoolAmount = None
|
||||
elif event.Id in self.cycleMap:
|
||||
cycles = self.cycleMap[event.Id]
|
||||
self.mod.spoolType = SpoolType.CYCLES
|
||||
self.mod.spoolAmount = cycles
|
||||
fitID = self.mainFrame.getActiveFit()
|
||||
Fit.getInstance().recalc(fitID)
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
|
||||
SpoolUp.register()
|
||||
@@ -1,24 +1,24 @@
|
||||
from gui.contextMenu import ContextMenu
|
||||
import gui.mainFrame
|
||||
import gui.globalEvents as GE
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.targetResists import TargetResists as svc_TargetResists
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
from collections import OrderedDict
|
||||
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
|
||||
import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuUnconditional
|
||||
from service.fit import Fit
|
||||
from service.settings import ContextMenuSettings
|
||||
from service.targetResists import TargetResists as svc_TargetResists
|
||||
|
||||
|
||||
class TargetResists(ContextMenuUnconditional):
|
||||
|
||||
class TargetResists(ContextMenu):
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
self.settings = ContextMenuSettings.getInstance()
|
||||
|
||||
def display(self, srcContext, selection):
|
||||
if not self.settings.get('targetResists'):
|
||||
return False
|
||||
|
||||
def display(self, srcContext):
|
||||
if self.mainFrame.getActiveFit() is None or srcContext != "firepowerViewFull":
|
||||
return False
|
||||
|
||||
@@ -28,7 +28,7 @@ class TargetResists(ContextMenu):
|
||||
|
||||
return len(self.patterns) > 0
|
||||
|
||||
def getText(self, itmContext, selection):
|
||||
def getText(self, itmContext):
|
||||
return "Target Resists"
|
||||
|
||||
def handleResistSwitch(self, event):
|
||||
@@ -43,7 +43,7 @@ class TargetResists(ContextMenu):
|
||||
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
|
||||
|
||||
def addPattern(self, rootMenu, pattern):
|
||||
id = ContextMenu.nextID()
|
||||
id = ContextMenuUnconditional.nextID()
|
||||
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
|
||||
|
||||
self.patternIds[id] = pattern
|
||||
@@ -64,7 +64,7 @@ class TargetResists(ContextMenu):
|
||||
item.SetBitmap(bitmap)
|
||||
return item
|
||||
|
||||
def getSubMenu(self, context, selection, rootMenu, i, pitem):
|
||||
def getSubMenu(self, context, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
self.patternIds = {}
|
||||
self.subMenus = OrderedDict()
|
||||
@@ -93,7 +93,7 @@ class TargetResists(ContextMenu):
|
||||
# Items that have a parent
|
||||
for menuName, patterns in list(self.subMenus.items()):
|
||||
# Create parent item for root menu that is simply name of parent
|
||||
item = wx.MenuItem(rootMenu, ContextMenu.nextID(), menuName)
|
||||
item = wx.MenuItem(rootMenu, ContextMenuUnconditional.nextID(), menuName)
|
||||
|
||||
# Create menu for child items
|
||||
grandSub = wx.Menu()
|
||||
|
||||
@@ -1 +1,13 @@
|
||||
__all__ = ["fitDps"]
|
||||
# noinspection PyUnresolvedReferences
|
||||
from gui.builtinGraphs import ( # noqa: E402,F401
|
||||
fitDpsRange,
|
||||
fitDpsTime,
|
||||
fitDmgTime,
|
||||
fitShieldRegenAmount,
|
||||
fitShieldAmountTime,
|
||||
fitCapRegenAmount,
|
||||
fitCapAmountTime,
|
||||
fitSpeedTime,
|
||||
fitDistanceTime,
|
||||
fitWarpTimeDistance
|
||||
)
|
||||
|
||||
81
gui/builtinGraphs/fitCapAmountTime.py
Normal file
81
gui/builtinGraphs/fitCapAmountTime.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitCapAmountTime import FitCapAmountTimeGraph as EosFitCapAmountTimeGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitCapAmountTimeGraph(Graph):
|
||||
|
||||
propertyLabelMap = {"time": "Time (seconds)"}
|
||||
|
||||
defaults = EosFitCapAmountTimeGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["time"] = "0-300"
|
||||
self.name = "Cap Amount vs Time"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
return self.defaults
|
||||
|
||||
def getLabels(self):
|
||||
return self.propertyLabelMap
|
||||
|
||||
def getIcons(self):
|
||||
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
return {"time": bitmap}
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitCapAmountTimeGraph(fit)
|
||||
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
if not d.isConstant():
|
||||
if variable is None:
|
||||
variable = fieldName
|
||||
else:
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
return x, y
|
||||
|
||||
|
||||
FitCapAmountTimeGraph.register()
|
||||
82
gui/builtinGraphs/fitCapRegenAmount.py
Normal file
82
gui/builtinGraphs/fitCapRegenAmount.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitCapRegenAmount import FitCapRegenAmountGraph as EosFitCapRegenAmountGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitCapRegenAmountGraph(Graph):
|
||||
|
||||
propertyLabelMap = {"percentage": "Cap Amount (percent)"}
|
||||
|
||||
defaults = EosFitCapRegenAmountGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["percentage"] = "0-100"
|
||||
self.name = "Cap Regen vs Cap Amount"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
return self.defaults
|
||||
|
||||
def getLabels(self):
|
||||
return self.propertyLabelMap
|
||||
|
||||
def getIcons(self):
|
||||
iconFile = Attribute.getInstance().getAttributeInfo('capacitorCapacity').iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
return {"percentage": bitmap}
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitCapRegenAmountGraph(fit)
|
||||
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
if not d.isConstant():
|
||||
if variable is None:
|
||||
variable = fieldName
|
||||
else:
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
|
||||
return x, y
|
||||
|
||||
|
||||
FitCapRegenAmountGraph.register()
|
||||
81
gui/builtinGraphs/fitDistanceTime.py
Normal file
81
gui/builtinGraphs/fitDistanceTime.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitDistanceTime import FitDistanceTimeGraph as EosFitDistanceTimeGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitDistanceTimeGraph(Graph):
|
||||
|
||||
propertyLabelMap = {"time": "Time (seconds)"}
|
||||
|
||||
defaults = EosFitDistanceTimeGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["time"] = "0-80"
|
||||
self.name = "Distance Traveled vs Time"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
return self.defaults
|
||||
|
||||
def getLabels(self):
|
||||
return self.propertyLabelMap
|
||||
|
||||
def getIcons(self):
|
||||
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
return {"time": bitmap}
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitDistanceTimeGraph(fit)
|
||||
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
if not d.isConstant():
|
||||
if variable is None:
|
||||
variable = fieldName
|
||||
else:
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
return x, y
|
||||
|
||||
|
||||
FitDistanceTimeGraph.register()
|
||||
82
gui/builtinGraphs/fitDmgTime.py
Normal file
82
gui/builtinGraphs/fitDmgTime.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitDmgTime import FitDmgTimeGraph as EosFitDmgTimeGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitDmgTimeGraph(Graph):
|
||||
|
||||
propertyLabelMap = {"time": "Time (seconds)"}
|
||||
|
||||
defaults = EosFitDmgTimeGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["time"] = "0-80"
|
||||
self.name = "Damage Inflicted vs Time"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
return self.defaults
|
||||
|
||||
def getLabels(self):
|
||||
return self.propertyLabelMap
|
||||
|
||||
def getIcons(self):
|
||||
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
return {"time": bitmap}
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitDmgTimeGraph(fit)
|
||||
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
if not d.isConstant():
|
||||
if variable is None:
|
||||
variable = fieldName
|
||||
else:
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
eosGraph.recalc()
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
return x, y
|
||||
|
||||
|
||||
FitDmgTimeGraph.register()
|
||||
@@ -17,15 +17,16 @@
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
from gui.graph import Graph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from eos.graph.fitDps import FitDpsGraph as FitDps
|
||||
from eos.graph import Data
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitDpsRange import FitDpsRangeGraph as EosFitDpsRangeGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitDpsGraph(Graph):
|
||||
class FitDpsRangeGraph(Graph):
|
||||
|
||||
propertyAttributeMap = {"angle": "maxVelocity",
|
||||
"distance": "maxRange",
|
||||
"signatureRadius": "signatureRadius",
|
||||
@@ -36,13 +37,13 @@ class FitDpsGraph(Graph):
|
||||
"signatureRadius": "Target Signature Radius (m)",
|
||||
"velocity": "Target Velocity (m/s)"}
|
||||
|
||||
defaults = FitDps.defaults.copy()
|
||||
defaults = EosFitDpsRangeGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["distance"] = "0-20"
|
||||
self.name = "DPS"
|
||||
self.fitDps = None
|
||||
self.defaults["distance"] = "0-100"
|
||||
self.name = "DPS vs Range"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
@@ -63,11 +64,11 @@ class FitDpsGraph(Graph):
|
||||
return icons
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
fitDps = getattr(self, "fitDps", None)
|
||||
if fitDps is None or fitDps.fit != fit:
|
||||
fitDps = self.fitDps = FitDps(fit)
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitDpsRangeGraph(fit)
|
||||
|
||||
fitDps.clearData()
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
@@ -78,18 +79,18 @@ class FitDpsGraph(Graph):
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
fitDps.setData(d)
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
for point, val in fitDps.getIterator():
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
|
||||
return x, y
|
||||
|
||||
|
||||
FitDpsGraph.register()
|
||||
FitDpsRangeGraph.register()
|
||||
83
gui/builtinGraphs/fitDpsTime.py
Normal file
83
gui/builtinGraphs/fitDpsTime.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# =============================================================================
|
||||
# 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/>.
|
||||
# =============================================================================
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.graph import Data
|
||||
from eos.graph.fitDpsTime import FitDpsTimeGraph as EosFitDpsTimeGraph
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.graph import Graph
|
||||
from service.attribute import Attribute
|
||||
|
||||
|
||||
class FitDpsTimeGraph(Graph):
|
||||
|
||||
propertyLabelMap = {"time": "Time (seconds)"}
|
||||
|
||||
defaults = EosFitDpsTimeGraph.defaults.copy()
|
||||
|
||||
def __init__(self):
|
||||
Graph.__init__(self)
|
||||
self.defaults["time"] = "0-80"
|
||||
self.name = "DPS vs Time"
|
||||
self.eosGraph = None
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
def getFields(self):
|
||||
return self.defaults
|
||||
|
||||
def getLabels(self):
|
||||
return self.propertyLabelMap
|
||||
|
||||
def getIcons(self):
|
||||
iconFile = Attribute.getInstance().getAttributeInfo('duration').iconID
|
||||
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
|
||||
return {"time": bitmap}
|
||||
|
||||
def getPoints(self, fit, fields):
|
||||
eosGraph = getattr(self, "eosGraph", None)
|
||||
if eosGraph is None or eosGraph.fit != fit:
|
||||
eosGraph = self.eosGraph = EosFitDpsTimeGraph(fit)
|
||||
|
||||
eosGraph.clearData()
|
||||
variable = None
|
||||
for fieldName, value in fields.items():
|
||||
d = Data(fieldName, value)
|
||||
if not d.isConstant():
|
||||
if variable is None:
|
||||
variable = fieldName
|
||||
else:
|
||||
# We can't handle more then one variable atm, OOPS FUCK OUT
|
||||
return False, "Can only handle 1 variable"
|
||||
|
||||
eosGraph.setData(d)
|
||||
|
||||
if variable is None:
|
||||
return False, "No variable"
|
||||
|
||||
x = []
|
||||
y = []
|
||||
eosGraph.recalc()
|
||||
for point, val in eosGraph.getIterator():
|
||||
x.append(point[variable])
|
||||
y.append(val)
|
||||
|
||||
return x, y
|
||||
|
||||
|
||||
FitDpsTimeGraph.register()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user