Compare commits

..

472 Commits

Author SHA1 Message Date
DarkPhoenix
71ae59b2b5 Bump version 2019-05-13 06:48:35 +03:00
DarkPhoenix
51294f6cbc Change the way graphs are imported 2019-05-13 06:46:25 +03:00
DarkPhoenix
0439ace886 UK -> US spelling 2019-05-12 18:35:23 +03:00
DarkPhoenix
c85c735f9a Bump version 2019-05-12 18:04:40 +03:00
DarkPhoenix
c65b582497 Remove dot after "vs" 2019-05-12 18:01:44 +03:00
DarkPhoenix
7f2ac83e17 Add context menu which removes fits from graphs 2019-05-12 17:42:56 +03:00
DarkPhoenix
5ef2a40d1e Delete fits by pressing del key in graphs window 2019-05-12 17:32:17 +03:00
DarkPhoenix
5b52da737a Do not trigger esc when modifier keys are pressed 2019-05-12 17:08:41 +03:00
DarkPhoenix
51e8713cd6 Add warp time graph 2019-05-12 17:04:11 +03:00
DarkPhoenix
c9b60f2c65 Add distance vs time graph 2019-05-12 16:26:02 +03:00
DarkPhoenix
d777999af4 Add speed vs time graph 2019-05-12 15:46:50 +03:00
DarkPhoenix
74444d56c4 Add shield amount vs time graph 2019-05-12 15:30:41 +03:00
DarkPhoenix
a433c9638a Add cap-time graph 2019-05-12 14:59:19 +03:00
DarkPhoenix
672141cffc Add cap amount graph 2019-05-12 14:40:57 +03:00
DarkPhoenix
ac132cbb92 Add shield recharge graph 2019-05-12 14:32:57 +03:00
DarkPhoenix
d9535b08b1 Rename graphs 2019-05-12 12:28:47 +03:00
DarkPhoenix
d93544b3bc Fix DPS over time graph 2019-05-12 06:46:28 +03:00
DarkPhoenix
2320c3cb57 Make sure to include all Y-values 2019-05-12 05:54:03 +03:00
DarkPhoenix
bd5710c676 Calculate graph data only once 2019-05-12 05:30:49 +03:00
DarkPhoenix
49f1412d91 Fix bug with drones 2019-05-12 04:56:59 +03:00
DarkPhoenix
54eea7d702 Rework damage over time graph to show actual damage dealt per volley 2019-05-12 04:44:27 +03:00
DarkPhoenix
e26bcb2e5e Move DoT logic into volley parameter fetcher 2019-05-12 03:15:10 +03:00
DarkPhoenix
7305c0a017 Add optional parameter to cycle parameter getters 2019-05-12 02:55:42 +03:00
DarkPhoenix
7d37b9e0e0 Implement volley parameters for modules 2019-05-12 02:49:21 +03:00
DarkPhoenix
87f28db730 Implement volley parameters for drones and fighters 2019-05-12 02:38:36 +03:00
DarkPhoenix
56d9a8b626 Rework fighter calculations to use cycle parameters 2019-05-12 02:18:44 +03:00
DarkPhoenix
cb8f76c582 Implement module cycle parameters logic for modules 2019-05-11 17:34:01 +03:00
DarkPhoenix
af0b7b92c7 Implement cycle parameters for drones 2019-05-11 16:15:03 +03:00
DarkPhoenix
9418b7a709 Calculate 0 range data points 2019-05-11 14:53:59 +03:00
DarkPhoenix
47c34f2186 Sort implant sets by name 2019-05-10 16:22:57 +03:00
DarkPhoenix
2ca418c287 Add damage over time graph 2019-05-10 03:18:25 +03:00
DarkPhoenix
775e69305c Make graphs switchable 2019-05-10 03:06:00 +03:00
DarkPhoenix
0f1cbb4234 Add support for dps over time graph 2019-05-10 02:46:50 +03:00
DarkPhoenix
306710a314 Bump version 2019-05-09 20:12:37 +03:00
DarkPhoenix
776a4ee977 Update effects 2019-05-09 20:11:31 +03:00
DarkPhoenix
9dccfd756a Update database to 1503340 2019-05-09 19:49:13 +03:00
DarkPhoenix
15281ee6ce Merge branch 'master' into singularity 2019-05-07 10:27:01 +03:00
DarkPhoenix
9a0dd6c521 Copy empty spots on fit as well when making copy 2019-05-06 20:21:28 +03:00
DarkPhoenix
a570f291ae Merge branch 'master' into singularity 2019-05-06 15:32:52 +03:00
DarkPhoenix
cde7fdcaba Do not activate MJD and MJFG upon fitting 2019-05-05 05:06:15 +03:00
DarkPhoenix
e4780bc8ba Merge branch 'master' into singularity 2019-05-04 12:18:50 +03:00
DarkPhoenix
4d35e5aee1 Comment out some conflicting jargon entries 2019-05-04 12:18:07 +03:00
DarkPhoenix
f7b705b9e2 Add focused void bomb jargon entry 2019-05-04 12:16:04 +03:00
DarkPhoenix
48f44cdb0c Merge branch 'master' into singularity 2019-05-04 02:44:12 +03:00
DarkPhoenix
013a2264c0 Show tooltip only if there's something to show 2019-05-04 02:43:33 +03:00
DarkPhoenix
8222686dda Enable tooltips for export options 2019-05-04 02:43:02 +03:00
DarkPhoenix
7f2121e98d Add possibility to export formatted DNA 2019-05-04 02:38:19 +03:00
DarkPhoenix
4b6c881dca Re-enable DNA export 2019-05-04 02:26:09 +03:00
DarkPhoenix
5f9bf4a861 Bump version 2019-05-03 22:41:10 +03:00
DarkPhoenix
154db5df0b Merge branch 'master' into singularity 2019-05-03 22:29:26 +03:00
DarkPhoenix
321b939d3a Commit mutated data before destroying window 2019-05-03 22:28:29 +03:00
DarkPhoenix
95a1d669f5 Invasion effects affect module mining cycle only 2019-05-03 19:01:10 +03:00
DarkPhoenix
9e3c9bd056 Merge branch 'master' into singularity 2019-05-03 16:30:38 +03:00
DarkPhoenix
bb9b3780ae Fix context submenu activation 2019-05-03 16:22:18 +03:00
DarkPhoenix
4c976d9f35 Scroll mutated item stats with mousewheel over spincontrol not just on GTK 2019-05-03 14:56:59 +03:00
DarkPhoenix
52a1314803 Merge branch 'master' into singularity 2019-05-03 04:25:27 +03:00
DarkPhoenix
a5475eb244 Do not activate ADC on fit import as well 2019-05-03 04:24:59 +03:00
DarkPhoenix
ba0a5db72f Remove stacking penalties from duration attribute
It cannot be stacking penalized
2019-05-03 04:04:58 +03:00
DarkPhoenix
2bac4a954f Merge branch 'master' into singularity 2019-05-03 04:04:11 +03:00
DarkPhoenix
e9f3453b04 Fix industrial core stacking penalties 2019-05-03 03:57:53 +03:00
DarkPhoenix
c950592b5b If corresponding option is enabled, re-enable all meta buttons on every search change rather than just on search beginning 2019-05-03 03:41:09 +03:00
DarkPhoenix
1cd42669a0 Merge branch 'master' into singularity 2019-05-03 03:37:48 +03:00
DarkPhoenix
2b24f14122 Change interface between commands and item containers once again 2019-05-03 03:36:47 +03:00
DarkPhoenix
4932b685e1 Merge branch 'master' into singularity 2019-05-03 02:31:22 +03:00
DarkPhoenix
cfffa1d99d Do not crash when facing unknown module in saved fit 2019-05-03 02:27:03 +03:00
DarkPhoenix
44a7e53b9e Bump version 2019-05-02 19:54:29 +03:00
DarkPhoenix
b35bdd4e33 Add triglavian invasion effects 2019-05-02 19:50:40 +03:00
DarkPhoenix
7f52f6fe44 Show spoolup time for MJFGs 2019-05-02 19:13:21 +03:00
DarkPhoenix
34e49da0c1 Add renders of new ships 2019-05-02 18:52:53 +03:00
DarkPhoenix
5132698974 Add draugur effect 2019-05-02 18:24:47 +03:00
DarkPhoenix
832cebcaaf Add ikitursa effects 2019-05-02 18:17:23 +03:00
DarkPhoenix
4eaccd1eed Change spoolup context menu to show 20-ish amount of items 2019-05-02 18:03:31 +03:00
DarkPhoenix
5245f289a5 Add nergal effects 2019-05-02 17:16:38 +03:00
DarkPhoenix
672aed44f2 Add trig frigate resist effects 2019-05-02 16:40:38 +03:00
DarkPhoenix
8c890cf9a5 Rerun effectUsedBy script 2019-05-02 16:30:28 +03:00
DarkPhoenix
8f9a95db93 Do not crash when icons are missing, and add ADC mutaplasmid override 2019-05-02 16:10:22 +03:00
DarkPhoenix
5a056e6d47 Update database to 1498791 2019-05-02 15:54:04 +03:00
DarkPhoenix
b121085271 Unfuck EFT import 2019-05-02 04:34:49 +03:00
DarkPhoenix
b3ef55cb7f Make modification of mutated stats undoable 2019-05-02 00:18:17 +03:00
DarkPhoenix
bbc8fd0f97 Change mutations UI to properly keep all needed elements visible and scroll when needed 2019-05-01 22:22:18 +03:00
DarkPhoenix
b11a576922 Fix an oversight which led to charges not unloading when replacing module 2019-05-01 17:59:59 +03:00
DarkPhoenix
7375258b9f Change citadel security status via fitting pane context menu 2019-05-01 16:44:08 +03:00
DarkPhoenix
c447cf06d7 Add context menu to change system security status 2019-05-01 16:22:26 +03:00
DarkPhoenix
a7dcf1ace6 Add commands which handle switching fit security status 2019-05-01 15:33:27 +03:00
DarkPhoenix
bd3d81e2f8 Plug fit security status in citadel rig effect 2019-05-01 15:22:01 +03:00
DarkPhoenix
732b7a5ab9 Add system security column to fits 2019-05-01 15:04:46 +03:00
DarkPhoenix
3c16600c53 Include sqlalchemy's baked in mac/windows specs as well 2019-05-01 14:54:49 +03:00
DarkPhoenix
d0921ba9ec Add extra import to include in distributives 2019-05-01 13:08:50 +03:00
DarkPhoenix
8ec840740b Change fix for #1909 2019-05-01 13:04:12 +03:00
DarkPhoenix
580ff1c435 Readability fixes 2019-05-01 01:28:41 +03:00
DarkPhoenix
d68296bfd7 Bump pyyaml version 2019-05-01 01:28:09 +03:00
DarkPhoenix
c520b5e4f5 Bump version 2019-05-01 01:08:47 +03:00
DarkPhoenix
2da85888be Fix scrolling speed in ship browser 2019-05-01 01:05:31 +03:00
DarkPhoenix
bdd4a8cfb7 Fix scroll rate in preference panel 2019-05-01 00:55:52 +03:00
DarkPhoenix
dbef3b8c84 Unfuck character editor context menus 2019-04-30 22:39:38 +03:00
DarkPhoenix
63a6d746e8 Search for DNA in chat line 2019-04-30 21:18:12 +03:00
DarkPhoenix
7b8d9f8dbe Round fitting resource attributes properly 2019-04-30 18:51:01 +03:00
DarkPhoenix
7aa73e4b2f Fix issue with putting ID instead of name into capping key 2019-04-30 18:48:53 +03:00
DarkPhoenix
33883c562a Collapse all children when collapsing market group 2019-04-30 17:37:16 +03:00
DarkPhoenix
36c17dde8d Show damage mod cumulative damage boost in misc column 2019-04-30 17:22:06 +03:00
DarkPhoenix
5a9fd188f0 Add ability to clone drones by ctrl-dragging them 2019-04-30 16:16:09 +03:00
DarkPhoenix
4b65662c9f Fix triage effect local tank stacking penalty 2019-04-30 15:37:27 +03:00
DarkPhoenix
cd3d1a9557 Add SBA jargon entry 2019-04-30 15:25:20 +03:00
DarkPhoenix
9f63b0b412 Import DNA-formatted fits from chat messages 2019-04-30 15:18:40 +03:00
DarkPhoenix
9249ef24b3 Allow to undo operations even if removed module doesn't fit (e.g. removing excessive modules on t3c with removed subs) 2019-04-30 14:39:11 +03:00
DarkPhoenix
63a599ca85 Restore removed dummies on all commands which can deal with t3c subsystems 2019-04-30 13:50:10 +03:00
DarkPhoenix
fa2bceaff9 Allow recalculating fit without changing slot layout (e.g. when t3c subs added/removed) 2019-04-30 13:21:39 +03:00
DarkPhoenix
e9dffeadf6 Undo removal of subsystems properly 2019-04-30 02:03:32 +03:00
DarkPhoenix
161c4629cf Do not paint subsystems with red when restrictions are disabled 2019-04-30 01:31:35 +03:00
DarkPhoenix
951ffcd35a Fix an oversight 2019-04-30 01:25:13 +03:00
DarkPhoenix
ba21ebe058 Restore mass-removed t3 modules properly 2019-04-30 01:23:52 +03:00
DarkPhoenix
f8c2604fb2 Do not crash when changing slot color option with item view unitialized 2019-04-30 00:47:04 +03:00
DarkPhoenix
966763aaa4 Make toggling ignore of restrictions undoable 2019-04-30 00:44:54 +03:00
DarkPhoenix
4eb8973c31 Rework how effect-blocking modules (scram) work to properly support undoing 2019-04-29 20:25:28 +03:00
DarkPhoenix
72fe52e560 Do not use GetItemData where we do not need it 2019-04-29 15:37:54 +03:00
DarkPhoenix
e346239174 Limit amount of commands by 100 2019-04-29 09:48:37 +03:00
DarkPhoenix
dd27a26fa9 Allow batch changes of projected fighter/module states 2019-04-29 09:47:38 +03:00
DarkPhoenix
162b115c91 Allow alt actions when removing projected items via double-click 2019-04-29 09:28:21 +03:00
DarkPhoenix
99f4ed6b33 Allow batch change of projected fighter variations 2019-04-29 09:17:46 +03:00
DarkPhoenix
53252241e1 Allow batch changes of projected drone variations 2019-04-29 08:46:01 +03:00
DarkPhoenix
698328e335 Allow batch changes of projected module metas 2019-04-29 07:51:13 +03:00
DarkPhoenix
dece788f66 Allow alt-switch of ability states for projected fighters 2019-04-29 01:18:15 +03:00
DarkPhoenix
958fbac582 Implement alt logic in projected item removal via context menu 2019-04-29 01:04:22 +03:00
DarkPhoenix
99d72956e8 Detect structure modules as modules 2019-04-29 00:43:18 +03:00
DarkPhoenix
eb601e991a Print "stack" suffix on remove context menu text depending on passed source context rather than item context 2019-04-29 00:25:02 +03:00
DarkPhoenix
cb7f0052c4 Do not remove items on Del key used with modifier keys 2019-04-29 00:02:29 +03:00
DarkPhoenix
8b75b5f184 Change projected view to support multi-selection 2019-04-29 00:00:07 +03:00
DarkPhoenix
bf5935e033 Alt + changing fighter state now changes state of similar fighters 2019-04-28 18:59:37 +03:00
DarkPhoenix
67e80deed9 Change command which changes projected item state to support multi-selection 2019-04-28 18:43:28 +03:00
DarkPhoenix
e39f9ffecf Rework projected removal command to be able to handle multi-selection 2019-04-27 23:27:16 +03:00
DarkPhoenix
e865c9a399 Use GetModifiers() instead of asking status of individual keys 2019-04-27 14:22:09 +03:00
Anton Vorobyov
a919510d07 Merge pull request #1955 from MaruMaruOO/master
Fixed AoE DD's signature radius for EFS exports.
2019-04-27 10:28:57 +03:00
MaruMaruOO
bd86d3289f Fixed AoE DD's signature radius for EFS exports. 2019-04-27 00:57:41 -04:00
DarkPhoenix
c8ff644b63 Make command fit panel multi-selectable 2019-04-25 23:55:41 +03:00
DarkPhoenix
6703a08976 Change command fit state changing command to support multi-selection 2019-04-25 19:27:36 +03:00
DarkPhoenix
3d70d9e37c Add multi-selection support to command fit remove command 2019-04-25 18:47:39 +03:00
DarkPhoenix
ef62d5cf97 Make boosters panel multi-selectable 2019-04-25 18:32:18 +03:00
DarkPhoenix
caf5f33c80 Change booster state changing command to support multi-selection 2019-04-25 18:13:24 +03:00
DarkPhoenix
29c29469c6 Change booster removal commands to support multi-selection 2019-04-25 17:56:53 +03:00
DarkPhoenix
7b564f1f53 Show "Change Ship Skills" even when clicking outside of module area 2019-04-25 17:38:31 +03:00
DarkPhoenix
f280955ac3 Do not change state of selection when state of item outside of selection is changed 2019-04-25 17:33:17 +03:00
DarkPhoenix
e09fce8411 Allow removal of several implants at once via context menu 2019-04-25 17:15:01 +03:00
DarkPhoenix
5777103d21 Allow batch implant state switching 2019-04-25 16:29:04 +03:00
DarkPhoenix
8671b20790 Rework implant state switch command to accept multiple positions 2019-04-25 15:20:20 +03:00
DarkPhoenix
dc30b3ed1d Change implant remove command to support batch removal 2019-04-25 14:56:09 +03:00
DarkPhoenix
07a9f77287 Allow to change batches of items in cargo 2019-04-25 13:36:54 +03:00
DarkPhoenix
9d58ceb14d Remove multiple cargo items at once via context menu 2019-04-25 09:56:09 +03:00
DarkPhoenix
71c421702c Rework code of remove item context menu a little 2019-04-25 09:50:56 +03:00
DarkPhoenix
989f12453d Remove multiple cargo items with delete key 2019-04-25 09:27:52 +03:00
DarkPhoenix
b7d6892d9f Rework change cargo meta command to support changing multiple items at once 2019-04-24 23:11:28 +03:00
DarkPhoenix
cfb351a751 Change cargo removal command to be able to accept multiple typeIDs 2019-04-24 22:09:45 +03:00
DarkPhoenix
dde1e7990d Allow batch manipulation of fighter abilities 2019-04-24 17:25:20 +03:00
DarkPhoenix
6e4ec54ac6 Allow batch toggling of fighters 2019-04-24 16:49:39 +03:00
DarkPhoenix
81da217a09 Allow batch meta level change on fighters 2019-04-24 16:28:40 +03:00
DarkPhoenix
2d1613d8bc Make sure that clicked item is always included in selection 2019-04-24 16:01:02 +03:00
DarkPhoenix
ccc2e6ece3 Make sure that drone variation is applicable to multiple modules, if multiple drones of the same type are selected 2019-04-24 15:56:19 +03:00
DarkPhoenix
1206e95cfb Rework meta switcher code 2019-04-24 15:37:15 +03:00
DarkPhoenix
921ccd3be9 Allow removal of batches of fighters 2019-04-24 15:17:30 +03:00
DarkPhoenix
178e3a3d56 Change sorting of fighters - group up by sub-roles as well 2019-04-24 13:28:14 +03:00
DarkPhoenix
5d255547e4 Make fighter panel multi-selectable and change fighter sorting to match order in fighter tube panel 2019-04-24 13:16:01 +03:00
DarkPhoenix
bd148b8792 Stylistic fixes 2019-04-24 12:47:50 +03:00
DarkPhoenix
b88ebdcfc0 Send context only for selected module in fitting panel 2019-04-24 12:45:21 +03:00
DarkPhoenix
3a26815d18 Change the fix again to not go into recursion problems on windows 2019-04-24 12:00:48 +03:00
DarkPhoenix
b70fcd9659 Change ScreenToClient fix 2019-04-24 11:46:20 +03:00
DarkPhoenix
71aa557770 Focus contents of various pages when user switches to their tabs 2019-04-24 09:02:28 +03:00
DarkPhoenix
d35bf6514f Do not pass selection as fallback, as we're now calculating clicked row properly 2019-04-24 08:16:23 +03:00
DarkPhoenix
abe015bee3 Add custom implementation of ScreenToClient 2019-04-24 08:11:37 +03:00
DarkPhoenix
929520091f Use wx' builtin when calculating relative coordinates 2019-04-23 23:50:49 +03:00
DarkPhoenix
d4847112a9 Make drone additions pane multi-selectable, change all related commands to support it too 2019-04-23 22:12:07 +03:00
DarkPhoenix
4e2c3a3fcc Do not select cargo/drone tab when adding drones/cargo fails 2019-04-23 19:21:31 +03:00
DarkPhoenix
91e6d89022 Update resource panel when addition panel tabs is selected as side-effect of context menu use 2019-04-23 19:18:33 +03:00
DarkPhoenix
b9a71c08b7 Change only modules which are supposed to change 2019-04-23 18:14:14 +03:00
DarkPhoenix
070dd62e6d Add alt support to variations menu for modules 2019-04-23 18:11:02 +03:00
DarkPhoenix
b404abca41 Add ability to remove similar modules to "remove module" context menu 2019-04-23 17:50:20 +03:00
DarkPhoenix
99f00b25a1 Change few details on how we handle charge switch 2019-04-23 17:39:48 +03:00
DarkPhoenix
45936b5b98 Change implant set context menu to single item as well 2019-04-23 16:34:40 +03:00
DarkPhoenix
b1aac9f56d Do not show menu items which are specific to modules when user clicks rack separator 2019-04-23 16:23:35 +03:00
DarkPhoenix
13f370ceb9 Change several context menus to use single-item class 2019-04-23 16:19:07 +03:00
DarkPhoenix
b5a4f97cb5 Change some context menus to use unconditional class 2019-04-23 16:09:12 +03:00
DarkPhoenix
0679a0af0f Change logic for selection-based context menus 2019-04-23 15:58:42 +03:00
DarkPhoenix
53fe3242b9 Start reworking context menus into several special classes to improve menu handling consistency 2019-04-23 15:38:53 +03:00
DarkPhoenix
6615bed1cd Fix context menu which is used to fill from market 2019-04-23 13:47:47 +03:00
DarkPhoenix
ad0c7a7a9d Rework conditions in some context menus to check mainItem when it's needed by the menu 2019-04-23 10:14:59 +03:00
DarkPhoenix
87ba6a9af0 Drop selection on ESC in fitting pane 2019-04-22 21:10:38 +03:00
DarkPhoenix
5c44df7f21 Unfuck state change for passive modules 2019-04-22 19:36:45 +03:00
DarkPhoenix
24bc675319 Do not crash when right-clicking without any selection 2019-04-22 19:26:24 +03:00
DarkPhoenix
be2e0b5de4 Rely on clicked item instead of selection where it makes sense 2019-04-22 17:33:40 +03:00
DarkPhoenix
e4481e8fb4 Do not rely on modeID, it doesn't always get set of fit by default when creating new fit 2019-04-22 17:04:30 +03:00
DarkPhoenix
19b1eb161b Pass main item argument to context menu definitions 2019-04-22 17:01:53 +03:00
DarkPhoenix
30ed1ac81d Do not crash on context menu when module which can have ammo and t3d mode are selected 2019-04-22 16:15:17 +03:00
DarkPhoenix
b4288e17e5 Send proper main module to context menu if possible 2019-04-22 16:06:16 +03:00
DarkPhoenix
c03d000c45 Pass item which is considered to be clicked item to context menus 2019-04-22 15:45:37 +03:00
DarkPhoenix
881ec8b5b4 Select all items in fitting panel on ctrl-a 2019-04-22 13:36:59 +03:00
DarkPhoenix
71d5b28b75 Reuse selected module getter in fitting panel code 2019-04-22 13:20:10 +03:00
DarkPhoenix
a15fdc3b23 Use shared unselection function when starting drag 2019-04-22 13:07:18 +03:00
DarkPhoenix
55cd33e653 Do not show "show in fitting browser" context menu when it doesn't do anything to avoid confusion 2019-04-22 12:36:10 +03:00
DarkPhoenix
af0b2b9f1b Do not crash when alt-clicking state column 2019-04-22 07:24:14 +03:00
DarkPhoenix
983641d1d5 If loading into specified group of modules fails during alt-drag, attempt to load into all 2019-04-22 00:43:52 +03:00
DarkPhoenix
4ab21e92bf Load charge into all modules when dropping onto illegal location 2019-04-22 00:36:48 +03:00
DarkPhoenix
413f00a475 Select just item being dragged when drag starts 2019-04-22 00:18:46 +03:00
DarkPhoenix
bde2043294 Do not crash when landing module on non-filled module slot while holding alt 2019-04-21 23:47:21 +03:00
DarkPhoenix
d45857f1fc Do not fill with module while adding anything via alt-number from market 2019-04-21 23:42:14 +03:00
DarkPhoenix
8a19bf78ce Fix changing projected fit amount to 0 2019-04-21 21:30:32 +03:00
DarkPhoenix
d523722988 Change projected fit context menu entry names 2019-04-21 21:26:58 +03:00
DarkPhoenix
031cb6fcfb Change projected fits behavior - now addition adds 1 fit in any case (even if fit is already projected), and removal via double-click works like with drones 2019-04-21 21:22:57 +03:00
DarkPhoenix
72fc560241 Make all secondary windows closable by ESC 2019-04-21 19:34:14 +03:00
DarkPhoenix
1d7be66eb1 Destroy stats window when it's closed 2019-04-21 18:30:25 +03:00
DarkPhoenix
5d32a31dc3 Fix implant editor 2019-04-21 18:23:56 +03:00
DarkPhoenix
4821bd1c72 Mass-replace modules when dragging something from market and dropping over another module while holding alt 2019-04-21 15:33:00 +03:00
DarkPhoenix
6694caafa0 Do not unselect on removing modules with delete key 2019-04-21 14:38:01 +03:00
DarkPhoenix
7b71c16cec Remove bunch of modules on alt-double click 2019-04-21 14:35:52 +03:00
DarkPhoenix
df6e7b5772 Remove charge changing functionality from module add command, as it wasn't used anyway 2019-04-21 11:36:21 +03:00
DarkPhoenix
7abc14eb7f Take into consideration effects as well 2019-04-21 11:30:24 +03:00
DarkPhoenix
4d21fa517a Reformat code a little 2019-04-21 11:22:59 +03:00
DarkPhoenix
f2a82c31c4 Add ability to fill fit with item, and fix some charge-related stuff 2019-04-21 10:53:26 +03:00
DarkPhoenix
337973965a Run state change on secondary modules even if clicked module is already in that state 2019-04-21 00:37:11 +03:00
DarkPhoenix
f0b3aafd54 Change state of similar mods when you have alt pressed 2019-04-21 00:32:37 +03:00
DarkPhoenix
f6b97859aa Move code which handles module filtering to command helpers 2019-04-21 00:24:50 +03:00
DarkPhoenix
1064a90a1c Allow to close all tabs with one action 2019-04-21 00:12:12 +03:00
DarkPhoenix
4c736de598 Revert change which allowed to use alt key as modifier to open fits in new tabs or switch modules to offline 2019-04-20 23:47:23 +03:00
DarkPhoenix
8d0ad26159 Change "open stats in the same window" modifier to use shift 2019-04-20 23:40:30 +03:00
DarkPhoenix
3efa07d821 When changing charges, check not just market group, but group too 2019-04-20 23:16:25 +03:00
DarkPhoenix
44240c1d37 Avoid committing more than once when restoring removed modules 2019-04-20 23:10:36 +03:00
DarkPhoenix
9eaeb60af7 Add ability to fill with modules by ctrl+alt+dragging 2019-04-20 22:54:19 +03:00
DarkPhoenix
cecb8f69a3 Change behavior of module state switching 2019-04-20 21:49:13 +03:00
DarkPhoenix
d402735c8b Accurately restore mod states on Undo(), if they were both changed manually and during state check on Do() 2019-04-20 21:22:34 +03:00
DarkPhoenix
327ad78eb8 Fix insert for boosters as well 2019-04-20 19:31:44 +03:00
DarkPhoenix
edc1ef0e38 Do not choke on context menu spawning when module cannot be found 2019-04-20 16:28:12 +03:00
DarkPhoenix
c6bfd0bc05 Do not refetch mouse state 2019-04-20 14:44:24 +03:00
DarkPhoenix
3badab0353 Fix various bugs replated to DB management 2019-04-20 14:29:10 +03:00
DarkPhoenix
8ca5b34c14 Insert instead of appending when we're asked to... 2019-04-20 13:30:07 +03:00
DarkPhoenix
417ffd396c Undo application of sets without committing results until operation is done 2019-04-20 12:59:48 +03:00
DarkPhoenix
ccb0732f7d Update preference tooltip 2019-04-20 04:24:55 +03:00
Anton Vorobyov
a994f55011 Merge pull request #1949 from pyfa-org/command_reliability
Command reliability
2019-04-20 04:20:25 +03:00
DarkPhoenix
c2d309430e Make sure cargo-module drag commands are using actual fit data too 2019-04-20 04:10:32 +03:00
DarkPhoenix
c3e1ec2760 Ensure that module-fit relationship is established before running some checks which can rely on them 2019-04-20 04:06:57 +03:00
DarkPhoenix
8c40489049 Fix wrong log message 2019-04-20 03:59:08 +03:00
DarkPhoenix
051800bc16 Do not break selection on delimiters when generating context menus 2019-04-20 03:49:09 +03:00
DarkPhoenix
d1a3e5c0e8 Fix some module-related command calls 2019-04-20 03:43:15 +03:00
DarkPhoenix
48d795676f Do not pass modules to charge switching commands 2019-04-20 02:52:28 +03:00
DarkPhoenix
3fec9ba173 Do not choke on mass projected fit removal, and restore projected fit amounts 2019-04-20 02:30:57 +03:00
DarkPhoenix
264208b42e Make sure projected drones do not choke on mass removal 2019-04-20 02:12:03 +03:00
DarkPhoenix
62e8da6ff2 Make fighter pane more reliable and change few already checked panels as well 2019-04-20 01:51:55 +03:00
DarkPhoenix
e3f21cf700 Make local drone commands reliable 2019-04-20 01:15:38 +03:00
DarkPhoenix
fbc34224bc Make implant-related command calls more reliable 2019-04-20 00:43:13 +03:00
DarkPhoenix
93cd3b97fa Make command-related commands and their invokations more reliable 2019-04-20 00:35:14 +03:00
DarkPhoenix
fda83bcb49 Check cargo command reliability 2019-04-20 00:19:17 +03:00
DarkPhoenix
85b046a640 Finish reworking booster command invokation 2019-04-20 00:07:27 +03:00
DarkPhoenix
1177575f77 Make item remove context menu safer 2019-04-19 23:12:29 +03:00
DarkPhoenix
fc4a10efe3 Fix booster removal 2019-04-19 22:57:06 +03:00
DarkPhoenix
f541b4329e Add extra checks to variations context menu 2019-04-19 20:05:33 +03:00
DarkPhoenix
7ba1a4c78f Avoid unnecessary calculation 2019-04-19 19:54:21 +03:00
DarkPhoenix
0675ed9a73 Remove drone stacks when holding alt 2019-04-19 19:40:59 +03:00
DarkPhoenix
68a13a6bb8 Use alt key in more actions as alternative or replacement to ctrl 2019-04-19 16:59:30 +03:00
DarkPhoenix
61ef7c3487 Make market item list single-selectable 2019-04-19 15:51:04 +03:00
DarkPhoenix
a5bb16c460 Add drone stack via ctrl-doubleclick 2019-04-19 13:56:17 +03:00
DarkPhoenix
e694ced86c Add drone stack context menu now also follows drone bandwidth limits 2019-04-19 13:34:04 +03:00
DarkPhoenix
babc2d1e42 Add 'stack' word to remove drone menu
Because that's what it does
2019-04-19 13:06:00 +03:00
DarkPhoenix
8e717b19d9 All lines are already unicode in py3 2019-04-19 12:46:26 +03:00
DarkPhoenix
dbca0f9dea Limit drone stacks by ship bandwidth too 2019-04-19 12:29:49 +03:00
DarkPhoenix
7380244cd9 Bump sqlalchemy requirement 2019-04-19 10:48:25 +03:00
DarkPhoenix
5b7c777d6b Add 2 functions which calculate how many drones ship can release / store 2019-04-19 09:36:28 +03:00
DarkPhoenix
2fb9d3479f When mass-changing charges, take not only items with the same typeID but all items from the same market group 2019-04-19 01:02:44 +03:00
DarkPhoenix
21f095250d Make it possible to fill with mutated modules 2019-04-18 21:36:07 +03:00
DarkPhoenix
270376e09c Swap variations and mutaplasmid conversion menu items 2019-04-18 21:21:59 +03:00
DarkPhoenix
1ed71c6580 Fix mistype in readme and add myself to contacts 2019-04-18 18:04:12 +03:00
DarkPhoenix
febc98045c Remove some GTK-specific close buttons (as gnome now has close titlebar in dialogs for a while) and resize some dialogs 2019-04-18 16:58:28 +03:00
DarkPhoenix
72ecc62732 Even bigger on linux! 2019-04-18 16:45:14 +03:00
DarkPhoenix
9e1681d3f9 Do not show boosters in implant editor search output 2019-04-18 16:41:18 +03:00
DarkPhoenix
0471ffa924 Change size of implant editor to fit some quite long hardwiring names (like em-8 series) 2019-04-18 16:38:57 +03:00
DarkPhoenix
4269a00428 Show deadspace variations before officer variations 2019-04-18 15:57:19 +03:00
DarkPhoenix
440b2caa8d Ensure that in variations menu, items are sorted by name within the same meta group and meta level 2019-04-18 15:54:56 +03:00
DarkPhoenix
7131cdbac6 Do not set position of empty module to None when we're attempting to convert it to dummy 2019-04-18 13:00:32 +03:00
DarkPhoenix
b70c1a7c6c Remove some deprecated code and rework handled list function 2019-04-18 13:00:06 +03:00
DarkPhoenix
f9fd265280 Add option which controls ammo switcher behavior 2019-04-18 11:34:16 +03:00
DarkPhoenix
9261d29ac1 DPS graph now respects factor reload option for fighters 2019-04-18 11:04:00 +03:00
DarkPhoenix
df658d4950 Changed default graph distancee to 0-100 km 2019-04-18 08:27:57 +03:00
DarkPhoenix
69f68bf4ea Always show implant set menu item if you have sets, even if implant is selected 2019-04-18 00:44:43 +03:00
DarkPhoenix
614f3acc7e Do not show civilian ammo for small lasers 2019-04-17 23:59:50 +03:00
DarkPhoenix
ec77acda1d Remove workaround used for orbital ammo, as it was removed from the game by CCP 2019-04-17 23:57:40 +03:00
DarkPhoenix
603553517e Bump version to 2.9.1 2019-04-17 22:41:43 +03:00
DarkPhoenix
4c04fd93ae Make sure position is returned as None in case the module cannot be found on passed fit 2019-04-17 22:37:44 +03:00
DarkPhoenix
f4f92b0821 Copy spoolup settings of modules when copying fit 2019-04-17 22:26:41 +03:00
DarkPhoenix
3aede13136 Copy t3d modes when copying fit 2019-04-17 22:24:49 +03:00
DarkPhoenix
d171548936 Fix fit creation 2019-04-17 20:02:05 +03:00
DarkPhoenix
3d0db365af Make projected modules overheatable again 2019-04-17 18:19:52 +03:00
DarkPhoenix
2716214816 Do not set mouse cursor on GTK as it's applied to whole panel for some reason 2019-04-17 17:05:16 +03:00
DarkPhoenix
91a46a2dd4 Move some code around 2019-04-17 16:22:09 +03:00
DarkPhoenix
9fd70a4ba9 Avoid using modPosition where possible 2019-04-17 16:14:30 +03:00
DarkPhoenix
400bb10ed3 Ensure that owner fit is always visible from all fit items and their modified attribute map
Fixes some issues with some afflictions not shown/registered when fit has just been loaded
2019-04-17 15:35:15 +03:00
DarkPhoenix
53915eb956 Minor cleanup 2019-04-17 13:27:08 +03:00
DarkPhoenix
5457c31148 Use already existing method in module validator 2019-04-17 10:03:49 +03:00
DarkPhoenix
be9ac7cc9c Bump version 2019-04-16 21:35:22 +03:00
DarkPhoenix
3e2d21ea44 Update siege effect 2019-04-16 21:33:49 +03:00
DarkPhoenix
be374f4eb4 Update regular effects 2019-04-16 21:02:43 +03:00
DarkPhoenix
ab200ef74b Update database to 1492195 2019-04-16 20:40:53 +03:00
DarkPhoenix
3e1ef6e76d Fix item diff script after we've changed the effects 2019-04-16 20:30:55 +03:00
DarkPhoenix
9fd0f016aa Merge branch 'master' of github.com:pyfa-org/Pyfa 2019-04-16 20:17:04 +03:00
DarkPhoenix
ae188826ae Update YAML file with icons from latest SDE 2019-04-16 20:15:41 +03:00
Anton Vorobyov
0736085676 Merge pull request #1925 from kiwimind/1924-typo
Amend typo on attribute tab on module stats window
2019-04-16 19:43:25 +03:00
Anton Vorobyov
fa4c48c6c2 Merge branch 'master' into 1924-typo 2019-04-16 19:43:13 +03:00
DarkPhoenix
82adccadda Always show factor reload time menu item 2019-04-16 19:41:52 +03:00
DarkPhoenix
f100971fbe Set charge to all modules when cmd/control is pressed, instead of having separate menu item 2019-04-16 19:35:24 +03:00
DarkPhoenix
af2da86f6f Change order of context menus 2019-04-16 19:26:45 +03:00
DarkPhoenix
5af211778b Rename context menu files 2019-04-16 18:16:53 +03:00
Glenn Barr
4c739308f9 Amend typo on attribute tab on module stats window 2019-04-16 16:16:36 +01:00
DarkPhoenix
98e834fd30 Re-add merge functionality 2019-04-16 17:23:25 +03:00
DarkPhoenix
7ae8d1dc1a Allow splitting drone stacks again 2019-04-16 16:53:22 +03:00
DarkPhoenix
e57fce39fe Fix fetching of prices of on-character implants 2019-04-16 16:25:44 +03:00
DarkPhoenix
fd54d2c28c Make sure module position is None when we're asking if it can be fit, fixes crash with cloning siege module-alike modules 2019-04-16 15:17:27 +03:00
DarkPhoenix
b5c411349c Never show that prices are being fetched for abyssal items 2019-04-16 15:12:36 +03:00
DarkPhoenix
4d31c8bb38 Send proper modadd/moddel events when changing mod meta level 2019-04-16 15:01:02 +03:00
DarkPhoenix
fbf3cace10 Save secondary state changes and restore them on undo 2019-04-16 14:48:25 +03:00
DarkPhoenix
30b12b04e8 Rework module to cargo GUI command 2019-04-16 14:07:54 +03:00
DarkPhoenix
9301bb56fa More changes to cargo to module command, start reworking module to cargo as well 2019-04-16 12:20:54 +03:00
DarkPhoenix
bc4c35665e Change the way dragging from cargo to modules is handled 2019-04-16 02:10:02 +03:00
DarkPhoenix
64bba0cfdb Rework cargo to module command 2019-04-16 01:41:19 +03:00
DarkPhoenix
a270dc44d2 Show attribute even if it's zero, if its base value is non-zero 2019-04-15 23:37:08 +03:00
DarkPhoenix
46fa1eb0c6 Fix implant set addition 2019-04-15 23:12:51 +03:00
DarkPhoenix
c7ed6367f9 Rework clone/swap modules 2019-04-15 21:00:34 +03:00
DarkPhoenix
9523c6f349 Add meta swap support to projected items 2019-04-15 20:03:00 +03:00
DarkPhoenix
8b0f5f871c Make sure drones to not merge on meta change 2019-04-15 18:57:00 +03:00
DarkPhoenix
3fa5ac7858 As commands now commit/recalc even in case of failure (as they do not know about exact reason of failure and we're not sure if rollback was succeeded), do not run them for every item added via double-click 2019-04-15 18:44:26 +03:00
DarkPhoenix
4c0f88cdfa Rework meta swap command 2019-04-15 18:05:18 +03:00
DarkPhoenix
0fedb17586 Fix implant panel selection issue 2019-04-15 14:06:57 +03:00
DarkPhoenix
960bef2b96 Move booster and implant meta swap out of meta swap command 2019-04-15 13:28:15 +03:00
DarkPhoenix
82777d0b02 Move gui commands to their own package to avoid confusion in commands package 2019-04-15 12:15:19 +03:00
DarkPhoenix
a560597a85 Rework 3 more UI commands 2019-04-15 12:06:07 +03:00
DarkPhoenix
59fb7bcd1b Do not show context menu item "open in new tab" for currently selected fit 2019-04-15 11:48:14 +03:00
DarkPhoenix
801f51b89c Rework projected GUI commands 2019-04-15 11:34:52 +03:00
DarkPhoenix
0b49bf201f Rework local module GUI commands 2019-04-14 22:47:00 +03:00
DarkPhoenix
cc8575b275 Rework local fighters GUI commands 2019-04-14 21:42:58 +03:00
DarkPhoenix
51a11edc36 Rework local drone GUI commands 2019-04-14 20:51:39 +03:00
DarkPhoenix
4a6c1c3920 Rework command-related GUI commands 2019-04-14 16:34:01 +03:00
DarkPhoenix
1cb0081420 Rework cargo-related GUI commands and use command when deleting item from cargo with delete key 2019-04-14 16:13:01 +03:00
DarkPhoenix
d61d69188f Do not store fit service and main frame on every command 2019-04-14 15:44:48 +03:00
DarkPhoenix
a829efa7ff Rework implant UI commands 2019-04-14 15:36:30 +03:00
DarkPhoenix
e6599d1a40 Rework GUI booster-related commands 2019-04-14 14:50:15 +03:00
DarkPhoenix
c4c68a4e93 Rename calc command package, as we're going to put gui commands into packages too 2019-04-14 14:21:43 +03:00
DarkPhoenix
f80244d560 Add custom command processor for our internal needs and use it in booster add command 2019-04-14 14:14:14 +03:00
DarkPhoenix
a8684ef1b9 Merge several local/projected commands into universal commands 2019-04-14 13:30:14 +03:00
DarkPhoenix
5ac9604fab Categorize other calc commands 2019-04-14 12:37:24 +03:00
DarkPhoenix
700e249bf3 Do not crash when undoing fit rename when fit item has been disposed 2019-04-14 11:55:22 +03:00
DarkPhoenix
c2b742304e Move some of commands into packages 2019-04-14 11:45:58 +03:00
DarkPhoenix
c2b0257449 Rework toggle commands 2019-04-14 11:37:58 +03:00
DarkPhoenix
0d90c187f2 Rename set mode command 2019-04-14 04:07:31 +03:00
DarkPhoenix
88acec4241 Show spoolup option for projected modules, and set spoolup via command processor for all cases 2019-04-14 03:59:59 +03:00
DarkPhoenix
9a6255cb29 Rework setting mode and fix command which changes fit name
It was just broken before
2019-04-14 03:18:25 +03:00
DarkPhoenix
c721869dfa Change charge changing command 2019-04-14 02:06:41 +03:00
DarkPhoenix
1154435a89 Minor style fixes 2019-04-13 23:26:53 +03:00
DarkPhoenix
6d9e60648e Do not remove items when quantity is 0 in fit commands, it will be done in gui commands instead 2019-04-13 23:11:28 +03:00
DarkPhoenix
4522edb814 Rework module state changing 2019-04-13 23:08:49 +03:00
DarkPhoenix
8139d88a52 Allow changing amounts of projected fighters 2019-04-13 22:48:20 +03:00
DarkPhoenix
353b845102 Change how changing of projected drone amount is done via commands 2019-04-13 22:05:06 +03:00
DarkPhoenix
c3bffcad34 Rework figter amount changer 2019-04-13 21:19:01 +03:00
DarkPhoenix
9e6031edf2 Get rid of drone variation command 2019-04-13 20:53:26 +03:00
DarkPhoenix
bb47df2119 Rework "change drone/cargo amount" commands 2019-04-13 20:33:23 +03:00
DarkPhoenix
aab9b39fea Slightly change how projected fits are processed 2019-04-13 18:47:47 +03:00
DarkPhoenix
1ca4c73a3e Rework projected fighter fit commands 2019-04-13 18:26:05 +03:00
DarkPhoenix
7f870cf675 Rework projected drone fit commands 2019-04-13 16:25:49 +03:00
DarkPhoenix
5f8ccb9831 Change fix for invisible labels on linux 2019-04-13 14:26:05 +03:00
DarkPhoenix
c94acd9718 If during price optimizations different items are converted into one, merge them 2019-04-13 03:19:34 +03:00
DarkPhoenix
1733d6abb8 Always show system-wide effect beacon on top 2019-04-13 02:52:59 +03:00
DarkPhoenix
aa9f16d3ad Make sure our handled lists are not sorted
Because we rely on item positions within lists to identify modules for undo/redo stuff
2019-04-13 02:46:54 +03:00
DarkPhoenix
c997661f3a Rework implant fit commands 2019-04-13 01:56:17 +03:00
DarkPhoenix
6174bf8a0b Refactor non-projected fighter commands 2019-04-13 01:27:26 +03:00
DarkPhoenix
36c19fe6e8 Rework local drone commands 2019-04-12 23:46:41 +03:00
DarkPhoenix
e6c9db3eef Change command-related commands a little 2019-04-12 22:24:13 +03:00
DarkPhoenix
336af0f669 Rework cargo to use cargoinfo 2019-04-12 20:09:13 +03:00
DarkPhoenix
2800637a90 Rework cargo fit commands 2019-04-12 19:51:18 +03:00
DarkPhoenix
1fb9936893 Fix booster commands 2019-04-12 17:55:56 +03:00
DarkPhoenix
d738ba615e Finish overhauling module-related fit commands 2019-04-12 14:32:25 +03:00
DarkPhoenix
e5a694384d Raise errors on all insertions to handled lists which fail 2019-04-12 12:41:46 +03:00
DarkPhoenix
1d98f889fd Raise exceptions on failures to add module to module list 2019-04-12 10:47:14 +03:00
DarkPhoenix
ad03f907fa Overhaul module-related commands 2019-04-12 04:02:28 +03:00
DarkPhoenix
5579929f83 Undoing removal of drones now restores their proper state 2019-04-11 18:20:56 +03:00
DarkPhoenix
e90e9a5ca1 Make removal of fighters completely undoable 2019-04-11 16:31:44 +03:00
DarkPhoenix
2341650437 Store projected fit state on deletion for undo purposes 2019-04-11 14:03:44 +03:00
DarkPhoenix
f2c26af791 Fix toggling of abilities of projected fighters 2019-04-11 13:45:25 +03:00
DarkPhoenix
fc82e45d6c As wormhole effects are still modules and we now fully support reverting modules even in case they were overwritten, consider them as modules in commands too 2019-04-11 13:21:49 +03:00
DarkPhoenix
1b54f07ce0 Fix small but significant oversight 2019-04-11 12:36:41 +03:00
DarkPhoenix
938fa11d13 Removed projected modules now keep their state for undoing 2019-04-11 10:45:09 +03:00
DarkPhoenix
a08aa77afc Restore state of command fits on undo 2019-04-11 00:54:42 +03:00
DarkPhoenix
4cbeb34a3e Make toggling fighter abilities undoable 2019-04-11 00:05:28 +03:00
DarkPhoenix
d2fab0a10e Make removal of fighters undoable w/o information loss 2019-04-10 23:42:05 +03:00
DarkPhoenix
a8c3612248 Make removal of item from cargo via double-click undoable 2019-04-10 23:16:46 +03:00
DarkPhoenix
50e4e6fdcf Fix undoing of drone removal 2019-04-10 23:01:43 +03:00
DarkPhoenix
69ada4f3ad Make sure we do not store any sqlalchemy-managed objects in projected command 2019-04-10 21:50:04 +03:00
DarkPhoenix
3fc77d03b4 Restore state of removed implants on undo 2019-04-10 19:32:48 +03:00
DarkPhoenix
8072bb600c Make toggling side-effects undoable 2019-04-10 18:47:46 +03:00
DarkPhoenix
fda2c43a1d Restore booster state even when it was replaced by another booster 2019-04-10 18:18:26 +03:00
DarkPhoenix
7fc98037a6 Save booster state upon deletion for undo 2019-04-10 17:59:34 +03:00
DarkPhoenix
b51c8d8ecf Focus projected pane when something is projected via context menu 2019-04-10 16:11:23 +03:00
DarkPhoenix
dca2db5a6d Make projected states undo'able 2019-04-10 15:44:03 +03:00
DarkPhoenix
fbb192404f Add undo support to muta->normal conversion 2019-04-10 09:51:07 +03:00
DarkPhoenix
7e41d8e20c Make conversion to mutaplasmid undoable 2019-04-10 09:14:22 +03:00
DarkPhoenix
9460998015 Make removing mutated items undoable 2019-04-10 01:22:41 +03:00
DarkPhoenix
86056bf282 Save all the info about mutations into command cache 2019-04-09 18:07:49 +03:00
Anton Vorobyov
bacb374ea4 Merge pull request #1914 from pyfa-org/menu_overhaul
Main menu & stats menu overhaul
2019-04-09 16:34:23 +03:00
DarkPhoenix
8d07f11aa4 Update shortcuts in main menu 2019-04-09 16:20:09 +03:00
DarkPhoenix
b6a13b32de Rework alt-key shortcuts 2019-04-09 15:55:10 +03:00
DarkPhoenix
1f1a16f896 Merge branch 'master' into menu_overhaul 2019-04-09 12:18:35 +03:00
Anton Vorobyov
444d7af7ed Merge pull request #1916 from pyfa-org/issue1887
Stats menu indicators
2019-04-09 12:13:43 +03:00
DarkPhoenix
e45e0b9444 Merge branch 'master' into menu_overhaul 2019-04-08 19:12:59 +03:00
DarkPhoenix
33886aea1b Add exotic plasma to damage patterns 2019-04-08 19:12:30 +03:00
DarkPhoenix
1ddc2edd88 Move loading defaults to preference buttons 2019-04-08 17:43:11 +03:00
DarkPhoenix
01b16ec5f0 Rework main menu by rearranging it into different groups 2019-04-08 16:32:07 +03:00
DarkPhoenix
ba337599c2 Do not activate ADC and PANIC upon fitting 2019-04-08 13:33:21 +03:00
DarkPhoenix
211a83defd Show market jump menu item only for items residing in groups shown by pyfa 2019-04-08 13:01:35 +03:00
Anton Vorobyov
091a7d7ab8 Merge pull request #1904 from burnsypet/issue-1871
Logic to enable/disable redo/undo buttons
2019-04-08 10:21:52 +03:00
Anton Vorobyov
b3030bacf9 Merge pull request #1913 from pyfa-org/market_metagroups
Market meta category options
2019-04-08 10:21:02 +03:00
DarkPhoenix
feb164a48e Reset item list to empty during search cancel only if we still were searching, do nothing otherwise 2019-04-08 00:16:58 +03:00
DarkPhoenix
b3a556e70a Raise error instead of doing nothing for abstract methods 2019-04-07 23:59:54 +03:00
DarkPhoenix
9d322ca862 Drop selection when search is used 2019-04-07 23:50:39 +03:00
DarkPhoenix
9bde08b1ce Rework price stats panel context menu 2019-04-07 23:33:22 +03:00
DarkPhoenix
953a67e2c4 Update market preferences icon 2019-04-07 21:00:57 +03:00
DarkPhoenix
2435e08dc0 Implement option to select all meta buttons on market group selection 2019-04-07 19:42:44 +03:00
DarkPhoenix
31645d20a5 As recents are int, do == instead of is 2019-04-05 21:57:01 +03:00
DarkPhoenix
5fa96c5e52 Introduce new option group and move some options around 2019-04-05 21:00:42 +03:00
DarkPhoenix
278d946f77 Fix price context menu 2019-04-05 20:22:24 +03:00
DarkPhoenix
9b8aa67271 Remove some old commented out code 2019-04-05 20:18:50 +03:00
DarkPhoenix
af197e56cf Revert "Remove price cache clear menu item"
This reverts commit d59111eef5.
2019-04-05 20:17:25 +03:00
DarkPhoenix
1d5ba89f2e Revert "Remove price options menu item"
This reverts commit f784f45b4e.
2019-04-05 20:17:13 +03:00
DarkPhoenix
f45ad5ceab Implement logic which temporarily enables/disables meta groups for search 2019-04-03 18:16:10 +03:00
DarkPhoenix
5dae614fa1 Split search and recent modes 2019-04-03 17:49:08 +03:00
DarkPhoenix
c144c16a7f Enable only available buttons in case of empty item list 2019-04-03 16:45:24 +03:00
DarkPhoenix
10425cd5d9 Do not run "emtpy item list" logic on market groups with no items at all 2019-04-03 16:41:33 +03:00
DarkPhoenix
1d208a0cf7 Move search mode reset on market jump to market browser from item view 2019-04-03 16:30:51 +03:00
DarkPhoenix
db07f9d534 Implement logic to autoselect some metagroups if no items were filtered 2019-04-03 15:58:18 +03:00
DarkPhoenix
ca4eeed8d1 Reword some preference descriptions 2019-04-03 15:36:28 +03:00
DarkPhoenix
292d95f6f8 Implement market jump meta category logic 2019-04-03 15:34:26 +03:00
burnsypet
bd5ee32227 change logic 2019-04-02 19:29:03 +01:00
DarkPhoenix
0a705d1d7f Change price panel padding a little 2019-04-02 20:00:53 +03:00
DarkPhoenix
be1bd24a05 Change layout of options and window size a little 2019-04-02 19:22:37 +03:00
DarkPhoenix
a9fb3501ac Change price preferences layout 2019-04-01 17:14:50 +03:00
DarkPhoenix
b8a8f9c422 Add metagroup-related preferences 2019-04-01 16:52:46 +03:00
DarkPhoenix
6c29b3f38b ALways include price of ship and modules 2019-04-01 15:41:38 +03:00
DarkPhoenix
f784f45b4e Remove price options menu item 2019-04-01 15:39:15 +03:00
DarkPhoenix
d59111eef5 Remove price cache clear menu item 2019-04-01 15:36:58 +03:00
DarkPhoenix
99a2a38f9c Merge branch 'master' into market_metagroups 2019-04-01 14:53:31 +03:00
DarkPhoenix
768c1d37b1 Fix fit export 2019-03-31 16:08:48 +03:00
Anton Vorobyov
b21cf5673f Merge pull request #1907 from MaruMaruOO/patch-1
Additional fix for #1902
2019-03-31 11:53:51 +03:00
MaruMaruOO
a6b8381e25 Additional fix for #1902
Adds checks to make sure effects return correct `isImplemented` and `isType` values when they are called before handler generation.
2019-03-30 09:15:48 -04:00
DarkPhoenix
7d8768ca3e Add extra check on command level to ensure no errors 2019-03-30 10:52:54 +03:00
DarkPhoenix
96e0a02d5c Ensure we do not attempt to import mutated module when we have no fit open 2019-03-30 10:33:01 +03:00
DarkPhoenix
6a2bdade80 Fix attr tab original value column fetching module attributes for charges 2019-03-29 01:09:52 +03:00
Ryan Holmes
ea9eb3a8b7 Merge branch 'origin_master' into issue1887 2019-03-26 18:53:19 -04:00
Ryan Holmes
9c5fe56981 Fix right click on state not toggling overheat 2019-03-26 18:46:26 -04:00
Ryan Holmes
8fed78d596 Add a few conditionals in case menu is spawned without a fit being loaded 2019-03-26 09:44:43 -04:00
Ryan Holmes
3c5846a983 Add a menu icon to stat panels which have a context menu associated with them 2019-03-26 09:37:03 -04:00
Ryan Holmes
fa0892032f Develop method for context menu classes to signal that they should be displayed, but disabled. This will allow us to determine if a context menu exists for a certain context (stats panel, for example) without relying on old logic of not displaying them if fitting is not loaded. 2019-03-26 09:36:31 -04:00
Ryan Holmes
ed7dd12258 Merge remote-tracking branch 'origin/context_menus' into origin_master 2019-03-26 09:32:19 -04:00
DarkPhoenix
1a5fc31e25 Switch to safeloader
Seems like we do not use any custom data structures in yaml
2019-03-26 08:10:40 +03:00
DarkPhoenix
facb0b1023 Fix EFS export 2019-03-26 08:00:57 +03:00
DarkPhoenix
6dd737cdd1 Revert "Fix EFS export"
This reverts commit 5d012d6959.
2019-03-26 07:58:53 +03:00
DarkPhoenix
5d012d6959 Fix EFS export 2019-03-26 07:57:51 +03:00
burnsypet
b00dbbbf42 Logic to enable/disable redo/undo 2019-03-25 19:31:10 +00:00
DarkPhoenix
b269381818 Include cargo and drones by default in total ship price 2019-03-25 16:14:48 +03:00
DarkPhoenix
fa72345bcf Add ship and modules as separate entries to menu for feature parity 2019-03-25 16:14:14 +03:00
DarkPhoenix
3229652efa Remove detailed price popups and move price preferences to new preference tab 2019-03-25 15:57:42 +03:00
Indiction
c02cccf415 Adding Option to Change "total price" calculation Statview
Hint for module and total price provide detail informations
2019-03-25 14:58:03 +03:00
DarkPhoenix
54277ebbda Merge branch 'master' into market_metagroups 2019-03-25 13:21:20 +03:00
DarkPhoenix
78c6d4f005 Merge branch 'master' of github.com:pyfa-org/Pyfa 2019-03-25 10:07:38 +03:00
DarkPhoenix
37ad2faa8e Fix fit XML export 2019-03-25 10:07:23 +03:00
Ryan Holmes
26cba1c47a Fix a long standing bug due to misuse of context menu bindings in stats panels (see #651, and probably others) 2019-03-24 21:48:05 -04:00
Ryan Holmes
f95420d7bf Fix character editor context menu on Linux (#1895) 2019-03-24 20:59:40 -04:00
Ryan Holmes
9c79fb666b Convert context menu spawning of "affected by" listing 2019-03-24 19:24:46 -04:00
Ryan Holmes
a9a9fd1b1a Clean up another context menu 2019-03-24 19:08:31 -04:00
Ryan Holmes
253d3107a0 First pass at cleaning up context menu events (#1895) 2019-03-24 18:55:25 -04:00
Ryan Holmes
656a7fc784 Do not attempted to remove from projected panel if nothing of interest is selected (#1899) 2019-03-24 16:59:18 -04:00
DarkPhoenix
ead8238d7e Merge branch 'master' into market_metagroups 2019-03-22 02:02:17 +03:00
DarkPhoenix
295368635b Move some of preferences into new group 2019-03-21 14:00:49 +03:00
415 changed files with 13483 additions and 8020 deletions

View File

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

View File

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

View File

@@ -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',
}
)
)

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

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

View File

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

View 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

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

BIN
eve.db

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View 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

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

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1,13 @@
__all__ = ["fitDps"]
# noinspection PyUnresolvedReferences
from gui.builtinGraphs import ( # noqa: E402,F401
fitDpsRange,
fitDpsTime,
fitDmgTime,
fitShieldRegenAmount,
fitShieldAmountTime,
fitCapRegenAmount,
fitCapAmountTime,
fitSpeedTime,
fitDistanceTime,
fitWarpTimeDistance
)

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

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

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

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

View File

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

View 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