Compare commits

...

113 Commits

Author SHA1 Message Date
DarkPhoenix
a54d70036b Bump version number 2020-01-21 15:32:16 +03:00
DarkPhoenix
9e67b5962c Update the way we update pip on appveyor 2020-01-21 15:31:48 +03:00
Anton Vorobyov
ed0c080163 Merge pull request #2123 from Neugeniko/master
Update Invasion Damage Profiles due to npc changes.
2020-01-21 15:26:53 +03:00
Neugeniko
21389d84fc Update Invasion Damage Profiles due to npc changes.
#2122
2020-01-21 23:17:37 +11:00
DarkPhoenix
0fe54631ad Update staticdata to 1651039 2020-01-21 15:00:21 +03:00
DarkPhoenix
36ea5200f2 Remove pyinstaller from requirements as it's needed just for packaging 2020-01-21 13:33:17 +03:00
DarkPhoenix
d9934160a6 Compatibility with pyinstaller 2020-01-21 13:30:48 +03:00
DarkPhoenix
b9c92c48d3 Bump wx version 2020-01-17 17:15:21 +03:00
DarkPhoenix
6d23df6156 Fix on-effect resistance definition 2020-01-17 16:08:55 +03:00
DarkPhoenix
a913ab72c2 Bump version of pyinstaller to avoid security issues 2020-01-17 11:14:28 +03:00
DarkPhoenix
f6d33a2ac1 Use effect IDs rather than effect amount when deciding if we should rebuild abilities 2020-01-16 19:02:10 +03:00
DarkPhoenix
5a3fec33a7 Bump version 2020-01-16 16:26:19 +03:00
DarkPhoenix
95734aca2c Add fighter AB effect 2020-01-16 16:19:37 +03:00
DarkPhoenix
377254be9d Add nirvana effects 2020-01-16 16:09:17 +03:00
DarkPhoenix
ad9905a5e0 Add shield booster tieritice conversions 2020-01-16 15:46:30 +03:00
DarkPhoenix
6fc5a9699e Add slave as jargon entry for amulets 2020-01-16 15:39:54 +03:00
DarkPhoenix
0ee86f4836 Add slaves to renames dictionary 2020-01-16 15:34:56 +03:00
DarkPhoenix
85593367f7 Remove single-precition workaround and update data to 1646988 2020-01-16 15:19:39 +03:00
DarkPhoenix
465ea61b06 Merge branch 'singularity' 2020-01-16 14:57:44 +03:00
DarkPhoenix
2d5507e951 Force faction structure neut to be variation of t1 item 2020-01-08 00:47:56 +03:00
DarkPhoenix
84e20be153 Add auto targeting system to list of mods which do not get online by default 2020-01-04 19:32:31 +03:00
DarkPhoenix
60080c02b0 Fix red giant ecm bonus 2020-01-02 22:11:16 +03:00
DarkPhoenix
2a0d4179d0 Make script effect passive 2020-01-02 22:03:05 +03:00
DarkPhoenix
6dd55e0cd4 Move sansha incursion and sleeper profile one level up, as there are not too many 2019-12-28 21:48:56 +03:00
Anton Vorobyov
0adc6e0c6f Merge pull request #2113 from Neugeniko/master
Add Abyssal/Invasion Damage and Target Profiles.
2019-12-28 21:45:51 +03:00
Neugeniko
e155356701 Add Invasion & Abyssal Target Profiles 2019-12-29 00:28:48 +11:00
Neugeniko
ef44b50980 Add Abyssal & Invasion Damage Profile 2019-12-29 00:26:39 +11:00
DarkPhoenix
6785d16c87 More mods not activated by default 2019-12-28 04:22:11 +03:00
DarkPhoenix
76c670b7d5 Do not activate several categories of modules on fitting 2019-12-28 03:53:02 +03:00
DarkPhoenix
717303a72f Allow to put separate charges into citadel cargo 2019-12-27 12:39:37 +03:00
DarkPhoenix
e13125b174 Enable sigamp tooltip for citadels 2019-12-27 12:35:51 +03:00
DarkPhoenix
8aa7964b81 Do not show effective cap stats for ships with 100% neut resist (citadels) 2019-12-27 12:27:30 +03:00
DarkPhoenix
5e5f23d296 Show DG standup ECM as variation of t1 module 2019-12-26 12:28:01 +03:00
DarkPhoenix
05837f99ff Change order of structure meta levels 2019-12-26 12:26:25 +03:00
DarkPhoenix
70f620c2c3 Fix unit name processing 2019-12-25 01:33:43 +03:00
DarkPhoenix
10ffb987ea Detect concord race and show appropriate icon 2019-12-23 17:02:34 +03:00
DarkPhoenix
13ed785551 Drop tank stats on factor reload toggle 2019-12-23 13:26:23 +03:00
DarkPhoenix
69d2e38647 Use modified item attributes to show ancillary tank modules reload time 2019-12-23 13:17:08 +03:00
DarkPhoenix
c45abbdbcf Use new table as source for skill requirement data 2019-12-19 20:44:17 +03:00
DarkPhoenix
2655b001a9 Add new skillrequirement file 2019-12-19 19:56:23 +03:00
DarkPhoenix
3c7f4edb1b Fix replacements generation 2019-12-19 16:41:49 +03:00
DarkPhoenix
663cbab401 More updates to the script, and data update as well 2019-12-19 16:24:45 +03:00
DarkPhoenix
897ca9ca43 Update DB script to be able to work with sisi data 2019-12-19 15:56:57 +03:00
DarkPhoenix
b31a071859 Fix crashes related to target profiles / damage patterns 2019-12-17 13:10:41 +03:00
DarkPhoenix
8c2788fd78 Do not show implant set editor if user cancels addition of an implant set 2019-12-16 18:06:03 +03:00
DarkPhoenix
dee8fa400c Allow to override menu visibility settings with control key 2019-12-16 16:29:42 +03:00
DarkPhoenix
2339327473 Do some renames 2019-12-16 13:59:41 +03:00
Anton Vorobyov
f6f9239dd5 Merge pull request #2103 from Gochim/issue/2073
Issue #2073: Added ability to add a list of implants from implant view to a new implant set
2019-12-16 13:55:00 +03:00
DarkPhoenix
cd4c8c8c10 Do not save changes when changes were reverted 2019-12-16 13:42:49 +03:00
DarkPhoenix
c25eda8b64 Do not save mutated values when user did not touch them (and they were altered due to rounding) 2019-12-16 13:11:18 +03:00
Gochim
b6edf0e034 Issue #2073: Fixed a stuck in character editor 2019-12-12 18:03:45 +02:00
Gochim
af4277fc7e Issue #2073: Fixed name of a popup menu item for implant tab 2019-12-12 17:57:03 +02:00
Gochim
acd774abe5 Issue #2073: Fixed Codacy warning 2019-12-12 14:02:31 +02:00
Gochim
9081353634 Issue #2073: Final tweaks on implementation 2019-12-11 07:51:31 +02:00
Gochim
9c7fa37a72 Issue #2073: Added functionality to add all implants in fit implant tab to implant set list 2019-12-10 18:14:13 +02:00
Gochim
d92e11893a Issue #2073: Added mechanism to pass implant list to implant set manager 2019-12-10 08:50:21 +02:00
Gochim
f55ab00bf5 Fixed error message text in EntityEditor 2019-12-10 08:18:01 +02:00
DarkPhoenix
36265aa2a3 Add comment about built-in pattern order 2019-12-07 15:52:10 +03:00
DarkPhoenix
bfedcdbffd Simplify code a little 2019-12-07 15:42:26 +03:00
DarkPhoenix
a5d10c4a76 Change sorting of built-in damage profiles 2019-12-07 15:41:07 +03:00
DarkPhoenix
2962ce1945 Ensure 0%-100% spoolup is sorted properly 2019-12-07 03:07:49 +03:00
DarkPhoenix
0063d2955e Add info on invasion ships 2019-12-07 02:53:48 +03:00
DarkPhoenix
9787a18666 Fix issue with inability to right-click user-defined target profiles in graph window 2019-12-07 02:47:24 +03:00
DarkPhoenix
d8cd3197b5 Show short profile name in graphs 2019-12-07 02:43:53 +03:00
DarkPhoenix
e07c4f65ab Allow to have square brackets in profile name 2019-12-07 02:19:24 +03:00
DarkPhoenix
c3108f773a Use uniform as default resists profile, if no resist profile can be fetched 2019-12-07 01:29:15 +03:00
DarkPhoenix
a400821268 Remove "database defaults" 2019-12-07 01:21:46 +03:00
DarkPhoenix
ca4bac07da Implement switching between user and builtin patterns 2019-12-07 01:17:41 +03:00
DarkPhoenix
0038eacc3f Add database migration to add new columns which are used to refer builting profiles 2019-12-07 00:50:35 +03:00
DarkPhoenix
4bd633a0d4 Process names on pattern objects themselves 2019-12-06 23:24:29 +03:00
DarkPhoenix
ee837f0b04 Add comment about ideal target profile 2019-12-06 20:07:08 +03:00
DarkPhoenix
016e18cc89 Set negative IDs to all hardcoded entities 2019-12-06 20:04:56 +03:00
DarkPhoenix
1d6ac53183 Make target profile adder multi-level 2019-12-06 20:00:53 +03:00
DarkPhoenix
1e32dfa463 Always show target resist switcher 2019-12-06 19:35:20 +03:00
DarkPhoenix
4431753570 Reimplement switcher to use built-ins too and be multi-level 2019-12-06 17:59:44 +03:00
DarkPhoenix
6fdb57318c Expose builtin target profiles to service 2019-12-06 17:14:12 +03:00
DarkPhoenix
649db019de Add builtin fetcher for target profiles 2019-12-06 16:58:58 +03:00
DarkPhoenix
1d528be0ef Add target profile builtins as well 2019-12-06 14:45:46 +03:00
DarkPhoenix
2d6f6df83d Store built-in damage patterns in dictionary rather than in list 2019-12-06 14:25:27 +03:00
DarkPhoenix
7fa998f276 Avoid duplicating names in context menu 2019-12-06 02:59:14 +03:00
DarkPhoenix
6a3157a4c8 Add hardcoded damage patterns to damage pattern definition module 2019-12-06 02:47:11 +03:00
DarkPhoenix
74efa50576 Add multi-level menu support for incoming damage patterns 2019-12-06 02:11:32 +03:00
DarkPhoenix
e99be78c54 Partially rework damage pattern switcher 2019-12-05 20:04:50 +03:00
DarkPhoenix
e386232de1 Gather data on damage patterns into single object 2019-12-05 10:44:26 +03:00
DarkPhoenix
a15896044d Show cap delta detailed info even when fit has no cap use 2019-12-05 10:14:30 +03:00
DarkPhoenix
72c74513ce Add excessive effective cap gain to tooltip 2019-12-04 22:21:25 +03:00
DarkPhoenix
34d6d13cb2 Avoid extra indentation in non-spool version of the tooltip 2019-12-03 20:01:25 +03:00
DarkPhoenix
323bb8e064 Change layout once again 2019-12-03 19:57:40 +03:00
DarkPhoenix
b628f8ef9b Change layout of the tooltip 2019-12-03 19:45:49 +03:00
DarkPhoenix
1900216d36 Show spoolup part before damage distribution 2019-12-03 16:31:21 +03:00
DarkPhoenix
c045ed81c1 Do not show spoolup mark when fit has no spoolup weapons 2019-12-03 16:21:16 +03:00
DarkPhoenix
39edec60e3 Add damage type info to firepower panel tooltips 2019-12-03 16:15:05 +03:00
Gochim
259214e907 Issue #2073: Added popup menu item 2019-12-03 09:23:54 +02:00
DarkPhoenix
2e1c53392d Keep hull energizers online upon addition 2019-12-02 11:41:13 +03:00
DarkPhoenix
7a77d12686 Use built-in curl timeouts instead of coreutils 2019-11-27 16:35:22 +03:00
DarkPhoenix
763a7714ed Use proper timeout on Mac OS 2019-11-27 16:06:31 +03:00
DarkPhoenix
e89f35d654 Do not fail Mac OS build if upload fails 2019-11-27 15:56:57 +03:00
DarkPhoenix
3196751b7a Bump version 2019-11-27 15:11:26 +03:00
DarkPhoenix
f221024974 Update staticdata to 1618828 2019-11-27 15:10:49 +03:00
DarkPhoenix
66cab8a0d4 Bump version 2019-11-26 16:55:15 +03:00
DarkPhoenix
daa49a8cd4 Correctly detect zirnitra as amarr 2019-11-26 16:54:47 +03:00
DarkPhoenix
b8601ff240 Update icons/renders 2019-11-26 16:50:51 +03:00
DarkPhoenix
5df3eeca64 Add zirnitra renders 2019-11-26 16:34:28 +03:00
DarkPhoenix
274e4ac2ca Update effects 2019-11-26 16:27:06 +03:00
DarkPhoenix
f6485251ca Update staticdata to 1617768 2019-11-26 15:00:40 +03:00
DarkPhoenix
c03fb70def Merge branch 'singularity' 2019-11-26 14:55:51 +03:00
DarkPhoenix
8652a2891b Do not attempt to change skill levels of built-in all0/5 chars 2019-11-22 15:46:29 +03:00
DarkPhoenix
a09af0a9eb Propagate keys to upper windows if they were not processed in here 2019-11-22 15:45:01 +03:00
Anton Vorobyov
c7d4b93fba Merge pull request #2094 from RussianCow/level-keyboard-shortcuts
Add keyboard shortcuts to character level editor
2019-11-22 15:01:26 +03:00
Sasha
745c0db530 Add keyboard shortcuts to character level editor 2019-11-21 18:10:56 -08:00
DarkPhoenix
bbb96a73b7 Add alias file for trig dread-related items 2019-11-06 23:09:28 +03:00
DarkPhoenix
e70ebad3f7 Bump version 2019-10-22 15:37:25 +03:00
DarkPhoenix
4dfe5c3abf Update database and effects to 1589866 2019-10-22 15:36:37 +03:00
176 changed files with 2393504 additions and 3262266 deletions

View File

@@ -45,7 +45,7 @@ install:
# Upgrade to the latest version of pip to avoid it displaying warnings
# about it being out of date.
- "pip install --disable-pip-version-check --user --upgrade pip"
- "python -m pip install --disable-pip-version-check --user --upgrade pip"
# Install the build dependencies of the project. If some dependencies contain
# compiled extensions and are not provided as pre-built wheel packages,

View File

@@ -72,7 +72,7 @@ def DBInMemory_test():
# noinspection PyPep8
#from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
# noinspection PyPep8
#from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, miscData, module, override, price, queries, skill, targetProfile, user
#from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, miscData, module, override, price, queries, skill, targetProfile, user
# If using in memory saveddata, you'll want to reflect it so the data structure is good.
if saveddata_connectionstring == "sqlite:///:memory:":

View File

@@ -32,7 +32,7 @@ DB_PATH = os.path.join(ROOT_DIR, 'eve.db')
JSON_DIR = os.path.join(ROOT_DIR, 'staticdata')
if ROOT_DIR not in sys.path:
sys.path.insert(0, ROOT_DIR)
GAMEDATA_SCHEMA_VERSION = 1
GAMEDATA_SCHEMA_VERSION = 3
def db_needs_update():
@@ -88,52 +88,164 @@ def update_db():
# Create the database tables
eos.db.gamedata_meta.create_all()
# Config dict
tables = {
'clonegrades': ('fsd_lite', eos.gamedata.AlphaCloneSkill),
'dogmaattributes': ('bulkdata', eos.gamedata.AttributeInfo),
'dogmaeffects': ('bulkdata', eos.gamedata.Effect),
'dogmatypeattributes': ('bulkdata', eos.gamedata.Attribute),
'dogmatypeeffects': ('bulkdata', eos.gamedata.ItemEffect),
'dogmaunits': ('bulkdata', eos.gamedata.Unit),
'evecategories': ('fsd_lite', eos.gamedata.Category),
'evegroups': ('fsd_lite', eos.gamedata.Group),
'metagroups': ('fsd_binary', eos.gamedata.MetaGroup),
'evetypes': ('fsd_lite', eos.gamedata.Item),
'traits': ('phobos', eos.gamedata.Traits),
'metadata': ('phobos', eos.gamedata.MetaData),
'marketgroups': ('fsd_binary', eos.gamedata.MarketGroup)}
def _readData(minerName, jsonName, keyIdName=None):
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
rawData = json.load(f)
if not keyIdName:
return rawData
# IDs in keys, rows in values
data = []
for k, v in rawData.items():
row = {}
row.update(v)
if keyIdName not in row:
row[keyIdName] = int(k)
data.append(row)
return data
fieldMapping = {
'marketgroups': {
'id': 'marketGroupID',
'name': 'marketGroupName'},
'metagroups': {
'id': 'metaGroupID'}}
def _addRows(data, cls, fieldMap=None):
if fieldMap is None:
fieldMap = {}
for row in data:
instance = cls()
for k, v in row.items():
if isinstance(v, str):
v = v.strip()
setattr(instance, fieldMap.get(k, k), v)
eos.db.gamedata_session.add(instance)
rowsInValues = (
'evetypes',
'evegroups',
'evecategories',
'marketgroups',
'metagroups')
def processEveTypes():
print('processing evetypes')
data = _readData('fsd_lite', 'evetypes', keyIdName='typeID')
for row in data:
if (
# Apparently people really want Civilian modules available
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor')
):
row['published'] = True
def convertIcons(data):
new = []
for k, v in list(data.items()):
v['iconID'] = k
new.append(v)
return new
def convertClones(data):
newData = []
for row in data:
if (
row['published'] or
# group Ship Modifiers, for items like tactical t3 ship modes
row['groupID'] == 1306 or
# Micro Bombs (Fighters)
row['typeID'] in (41549, 41548, 41551, 41550) or
# Abyssal weather (environment)
row['groupID'] in (
1882,
1975,
1971,
# the "container" for the abyssal environments
1983)
):
newData.append(row)
_addRows(newData, eos.gamedata.Item)
return newData
def processEveGroups():
print('processing evegroups')
data = _readData('fsd_lite', 'evegroups', keyIdName='groupID')
_addRows(data, eos.gamedata.Group)
return data
def processEveCategories():
print('processing evecategories')
data = _readData('fsd_lite', 'evecategories', keyIdName='categoryID')
_addRows(data, eos.gamedata.Category)
def processDogmaAttributes():
print('processing dogmaattributes')
data = _readData('fsd_binary', 'dogmaattributes', keyIdName='attributeID')
_addRows(data, eos.gamedata.AttributeInfo)
def processDogmaTypeAttributes(eveTypesData):
print('processing dogmatypeattributes')
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
eveTypeIds = set(r['typeID'] for r in eveTypesData)
newData = []
for row in eveTypesData:
for attrId, attrName in {4: 'mass', 38: 'capacity', 161: 'volume', 162: 'radius'}.items():
if attrName in row:
newData.append({'typeID': row['typeID'], 'attributeID': attrId, 'value': row[attrName]})
for typeData in data:
if typeData['typeID'] not in eveTypeIds:
continue
for row in typeData.get('dogmaAttributes', ()):
row['typeID'] = typeData['typeID']
newData.append(row)
_addRows(newData, eos.gamedata.Attribute)
return newData
def processDynamicItemAttributes():
print('processing dynamicitemattributes')
data = _readData('fsd_binary', 'dynamicitemattributes')
for mutaID, mutaData in data.items():
muta = eos.gamedata.DynamicItem()
muta.typeID = mutaID
muta.resultingTypeID = mutaData['inputOutputMapping'][0]['resultingType']
eos.db.gamedata_session.add(muta)
for x in mutaData['inputOutputMapping'][0]['applicableTypes']:
item = eos.gamedata.DynamicItemItem()
item.typeID = mutaID
item.applicableTypeID = x
eos.db.gamedata_session.add(item)
for attrID, attrData in mutaData['attributeIDs'].items():
attr = eos.gamedata.DynamicItemAttribute()
attr.typeID = mutaID
attr.attributeID = attrID
attr.min = attrData['min']
attr.max = attrData['max']
eos.db.gamedata_session.add(attr)
def processDogmaEffects():
print('processing dogmaeffects')
data = _readData('fsd_binary', 'dogmaeffects', keyIdName='effectID')
_addRows(data, eos.gamedata.Effect, fieldMap={'resistanceAttributeID': 'resistanceID'})
def processDogmaTypeEffects(eveTypesData):
print('processing dogmatypeeffects')
data = _readData('fsd_binary', 'typedogma', keyIdName='typeID')
eveTypeIds = set(r['typeID'] for r in eveTypesData)
newData = []
for typeData in data:
if typeData['typeID'] not in eveTypeIds:
continue
for row in typeData.get('dogmaEffects', ()):
row['typeID'] = typeData['typeID']
newData.append(row)
_addRows(newData, eos.gamedata.ItemEffect)
return newData
def processDogmaUnits():
print('processing dogmaunits')
data = _readData('fsd_binary', 'dogmaunits', keyIdName='unitID')
_addRows(data, eos.gamedata.Unit, fieldMap={'name': 'unitName'})
def processMarketGroups():
print('processing marketgroups')
data = _readData('fsd_binary', 'marketgroups', keyIdName='marketGroupID')
_addRows(data, eos.gamedata.MarketGroup, fieldMap={'name': 'marketGroupName'})
def processMetaGroups():
print('processing metagroups')
data = _readData('fsd_binary', 'metagroups', keyIdName='metaGroupID')
_addRows(data, eos.gamedata.MetaGroup)
def processCloneGrades():
print('processing clonegrades')
data = _readData('fsd_lite', 'clonegrades')
newData = []
# December, 2017 - CCP decided to use only one set of skill levels for alpha clones. However, this is still
# represented in the data as a skillset per race. To ensure that all skills are the same, we store them in a way
# that we can check to make sure all races have the same skills, as well as skill levels
check = {}
for ID in data:
for skill in data[ID]['skills']:
newData.append({
@@ -144,18 +256,25 @@ def update_db():
if ID not in check:
check[ID] = {}
check[ID][int(skill['typeID'])] = int(skill['level'])
if not functools.reduce(lambda a, b: a if a == b else False, [v for _, v in check.items()]):
raise Exception('Alpha Clones not all equal')
newData = [x for x in newData if x['alphaCloneID'] == 1]
if len(newData) == 0:
raise Exception('Alpha Clone processing failed')
return newData
tmp = []
for row in newData:
if row['alphaCloneID'] not in tmp:
cloneParent = eos.gamedata.AlphaClone()
setattr(cloneParent, 'alphaCloneID', row['alphaCloneID'])
setattr(cloneParent, 'alphaCloneName', row['alphaCloneName'])
eos.db.gamedata_session.add(cloneParent)
tmp.append(row['alphaCloneID'])
_addRows(newData, eos.gamedata.AlphaCloneSkill)
def convertTraits(data):
def processTraits():
print('processing traits')
data = _readData('phobos', 'traits')
def convertSection(sectionData):
sectionLines = []
@@ -182,9 +301,42 @@ def update_db():
traitLine = '<br />\n<br />\n'.join(typeLines)
newRow = {'typeID': typeId, 'traitText': traitLine}
newData.append(newRow)
return newData
def fillReplacements(tables):
_addRows(newData, eos.gamedata.Traits)
def processMetadata():
print('processing metadata')
data = _readData('phobos', 'metadata')
_addRows(data, eos.gamedata.MetaData)
def processReqSkills(eveTypesData):
print('processing requiredskillsfortypes')
def composeReqSkills(raw):
reqSkills = {}
for skillTypeID, skillLevels in raw.items():
reqSkills[int(skillTypeID)] = skillLevels[0]
return reqSkills
eveTypeIds = set(r['typeID'] for r in eveTypesData)
data = _readData('fsd_binary', 'requiredskillsfortypes')
reqsByItem = {}
itemsByReq = {}
for typeID, skillreqData in data.items():
typeID = int(typeID)
if typeID not in eveTypeIds:
continue
for skillTypeID, skillLevel in composeReqSkills(skillreqData).items():
reqsByItem.setdefault(typeID, {})[skillTypeID] = skillLevel
itemsByReq.setdefault(skillTypeID, {})[typeID] = skillLevel
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
if item.typeID in reqsByItem:
item.reqskills = json.dumps(reqsByItem[item.typeID])
if item.typeID in itemsByReq:
item.requiredfor = json.dumps(itemsByReq[item.typeID])
def processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData):
print('finding item replacements')
def compareAttrs(attrs1, attrs2):
# Consider items as different if they have no attrs
@@ -196,7 +348,6 @@ def update_db():
return True
return False
print('finding replacements')
skillReqAttribs = {
182: 277,
183: 278,
@@ -208,18 +359,18 @@ def update_db():
# Get data on type groups
# Format: {type ID: group ID}
typesGroups = {}
for row in tables['evetypes']:
for row in eveTypesData:
typesGroups[row['typeID']] = row['groupID']
# Get data on item effects
# Format: {type ID: set(effect, IDs)}
typesEffects = {}
for row in tables['dogmatypeeffects']:
for row in dogmaTypeEffectsData:
typesEffects.setdefault(row['typeID'], set()).add(row['effectID'])
# Get data on type attributes
# Format: {type ID: {attribute ID: attribute value}}
typesNormalAttribs = {}
typesSkillAttribs = {}
for row in tables['dogmatypeattributes']:
for row in dogmaTypeAttributesData:
attributeID = row['attributeID']
if attributeID in skillReqAttribsFlat:
typeSkillAttribs = typesSkillAttribs.setdefault(row['typeID'], {})
@@ -258,13 +409,13 @@ def update_db():
typeSkillReqs[skillType] = skillLevel
# Format: {group ID: category ID}
groupCategories = {}
for row in tables['evegroups']:
for row in eveGroupsData:
groupCategories[row['groupID']] = row['categoryID']
# As EVE affects various types mostly depending on their group or skill requirements,
# we're going to group various types up this way
# Format: {(group ID, frozenset(skillreq, type, IDs), frozenset(type, effect, IDs): [type ID, {attribute ID: attribute value}]}
groupedData = {}
for row in tables['evetypes']:
for row in eveTypesData:
typeID = row['typeID']
# Ignore items outside of categories we need
if groupCategories[typesGroups[typeID]] not in (
@@ -301,134 +452,30 @@ def update_db():
if compareAttrs(type1[1], type2[1]):
replacements.setdefault(type1[0], set()).add(type2[0])
replacements.setdefault(type2[0], set()).add(type1[0])
# Put this data into types table so that normal process hooks it up
for row in tables['evetypes']:
row['replacements'] = ','.join('{}'.format(tid) for tid in sorted(replacements.get(row['typeID'], ())))
# Update DB session with data we generated
for item in eos.db.gamedata_session.query(eos.gamedata.Item).all():
itemReplacements = replacements.get(item.typeID)
if itemReplacements is not None:
item.replacements = ','.join('{}'.format(tid) for tid in sorted(itemReplacements))
data = {}
eveTypesData = processEveTypes()
eveGroupsData = processEveGroups()
processEveCategories()
processDogmaAttributes()
dogmaTypeAttributesData = processDogmaTypeAttributes(eveTypesData)
processDynamicItemAttributes()
processDogmaEffects()
dogmaTypeEffectsData = processDogmaTypeEffects(eveTypesData)
processDogmaUnits()
processMarketGroups()
processMetaGroups()
processCloneGrades()
processTraits()
processMetadata()
# Dump all data to memory so we can easely cross check ignored rows
for jsonName, (minerName, cls) in tables.items():
with open(os.path.join(JSON_DIR, minerName, '{}.json'.format(jsonName)), encoding='utf-8') as f:
tableData = json.load(f)
if jsonName in rowsInValues:
newTableData = []
for k, v in tableData.items():
row = {}
row.update(v)
if 'id' not in row:
row['id'] = int(k)
newTableData.append(row)
tableData = newTableData
if jsonName == 'icons':
tableData = convertIcons(tableData)
if jsonName == 'traits':
tableData = convertTraits(tableData)
if jsonName == 'clonegrades':
tableData = convertClones(tableData)
data[jsonName] = tableData
fillReplacements(data)
# Set with typeIDs which we will have in our database
# Sometimes CCP unpublishes some items we want to have published, we
# can do it here - just add them to initial set
eveTypes = set()
for row in data['evetypes']:
if (
row['published'] or
row['typeName'] == 'Capsule' or
# group Ship Modifiers, for items like tactical t3 ship modes
row['groupID'] == 1306 or
# Civilian weapons
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
# Micro Bombs (Fighters)
row['typeID'] in (41549, 41548, 41551, 41550) or
# Abyssal weather (environment)
row['groupID'] in (
1882,
1975,
1971,
# the "container" for the abyssal environments
1983) or
# Dark Blood Tracking Disruptor (drops, but rarely)
row['typeID'] == 32416
):
eveTypes.add(row['typeID'])
# ignore checker
def isIgnored(file, row):
if file in ('evetypes', 'dogmatypeeffects', 'dogmatypeattributes') and row['typeID'] not in eveTypes:
return True
return False
# Loop through each json file and write it away, checking ignored rows
for jsonName, table in data.items():
fieldMap = fieldMapping.get(jsonName, {})
tmp = []
print('processing {}'.format(jsonName))
for row in table:
# We don't care about some kind of rows, filter it out if so
if not isIgnored(jsonName, row):
if (
jsonName == 'evetypes' and (
# Apparently people really want Civilian modules available
(row['typeName'].startswith('Civilian') and "Shuttle" not in row['typeName']) or
row['typeName'] in ('Capsule', 'Dark Blood Tracking Disruptor'))
):
row['published'] = True
instance = tables[jsonName][1]()
# fix for issue 80
if jsonName is 'icons' and 'res:/ui/texture/icons/' in str(row['iconFile']).lower():
row['iconFile'] = row['iconFile'].lower().replace('res:/ui/texture/icons/', '').replace('.png', '')
# with res:/ui... references, it points to the actual icon file (including it's size variation of #_size_#)
# strip this info out and get the identifying info
split = row['iconFile'].split('_')
if len(split) == 3:
row['iconFile'] = '{}_{}'.format(split[0], split[2])
if jsonName is 'icons' and 'modules/' in str(row['iconFile']).lower():
row['iconFile'] = row['iconFile'].lower().replace('modules/', '').replace('.png', '')
if jsonName is 'clonegrades':
if row['alphaCloneID'] not in tmp:
cloneParent = eos.gamedata.AlphaClone()
setattr(cloneParent, 'alphaCloneID', row['alphaCloneID'])
setattr(cloneParent, 'alphaCloneName', row['alphaCloneName'])
eos.db.gamedata_session.add(cloneParent)
tmp.append(row['alphaCloneID'])
for k, v in row.items():
if isinstance(v, str):
v = v.strip()
setattr(instance, fieldMap.get(k, k), v)
eos.db.gamedata_session.add(instance)
# quick and dirty hack to get this data in
with open(os.path.join(JSON_DIR, 'fsd_binary', 'dynamicitemattributes.json'), encoding='utf-8') as f:
bulkdata = json.load(f)
for mutaID, data in bulkdata.items():
muta = eos.gamedata.DynamicItem()
muta.typeID = mutaID
muta.resultingTypeID = data['inputOutputMapping'][0]['resultingType']
eos.db.gamedata_session.add(muta)
for x in data['inputOutputMapping'][0]['applicableTypes']:
item = eos.gamedata.DynamicItemItem()
item.typeID = mutaID
item.applicableTypeID = x
eos.db.gamedata_session.add(item)
for attrID, attrData in data['attributeIDs'].items():
attr = eos.gamedata.DynamicItemAttribute()
attr.typeID = mutaID
attr.attributeID = attrID
attr.min = attrData['min']
attr.max = attrData['max']
eos.db.gamedata_session.add(attr)
eos.db.gamedata_session.flush()
processReqSkills(eveTypesData)
processReplacements(eveTypesData, eveGroupsData, dogmaTypeAttributesData, dogmaTypeEffectsData)
# Add schema version to prevent further updates
metadata_schema_version = eos.gamedata.MetaData()
@@ -436,7 +483,7 @@ def update_db():
metadata_schema_version.field_value = GAMEDATA_SCHEMA_VERSION
eos.db.gamedata_session.add(metadata_schema_version)
eos.db.gamedata_session.commit()
eos.db.gamedata_session.flush()
# CCP still has 5 subsystems assigned to T3Cs, even though only 4 are available / usable. They probably have some
# old legacy requirement or assumption that makes it difficult for them to change this value in the data. But for

View File

@@ -1,78 +0,0 @@
# This apes hook-matplotlib.backends.py, but REMOVES backends, all but
# the ones in the list below.
# Courtesy of https://github.com/bpteague/cytoflow/blob/70f9291/packaging/hook-matplotlib.backends.py
KEEP = ["WXAgg", "WX", "agg"]
from PyInstaller.compat import is_darwin
from PyInstaller.utils.hooks import (
eval_statement, exec_statement, logger)
def get_matplotlib_backend_module_names():
"""
List the names of all matplotlib backend modules importable under the
current Python installation.
Returns
----------
list
List of the fully-qualified names of all such modules.
"""
# Statement safely importing a single backend module.
import_statement = """
import os, sys
# Preserve stdout.
sys_stdout = sys.stdout
try:
# Redirect output printed by this importation to "/dev/null", preventing
# such output from being erroneously interpreted as an error.
with open(os.devnull, 'w') as dev_null:
sys.stdout = dev_null
__import__('%s')
# If this is an ImportError, print this exception's message without a traceback.
# ImportError messages are human-readable and require no additional context.
except ImportError as exc:
sys.stdout = sys_stdout
print(exc)
# Else, print this exception preceded by a traceback. traceback.print_exc()
# prints to stderr rather than stdout and must not be called here!
except Exception:
sys.stdout = sys_stdout
import traceback
print(traceback.format_exc())
"""
# List of the human-readable names of all available backends.
backend_names = eval_statement(
'import matplotlib; print(matplotlib.rcsetup.all_backends)')
# List of the fully-qualified names of all importable backend modules.
module_names = []
# If the current system is not OS X and the "CocoaAgg" backend is available,
# remove this backend from consideration. Attempting to import this backend
# on non-OS X systems halts the current subprocess without printing output
# or raising exceptions, preventing its reliable detection.
if not is_darwin and 'CocoaAgg' in backend_names:
backend_names.remove('CocoaAgg')
# For safety, attempt to import each backend in a unique subprocess.
for backend_name in backend_names:
if backend_name in KEEP:
continue
module_name = 'matplotlib.backends.backend_%s' % backend_name.lower()
stdout = exec_statement(import_statement % module_name)
# If no output was printed, this backend is importable.
if not stdout:
module_names.append(module_name)
logger.info(' Matplotlib backend "%s": removed' % backend_name)
return module_names
# Freeze all importable backends, as PyInstaller is unable to determine exactly
# which backends are required by the current program.
e=get_matplotlib_backend_module_names()
print(e)
excludedimports = e

View File

@@ -85,7 +85,7 @@ pyfalog.debug('Importing gamedata DB scheme')
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
pyfalog.debug('Importing saveddata DB scheme')
# noinspection PyPep8
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \
miscData, mutator, module, override, price, queries, skill, targetProfile, user
pyfalog.debug('Importing gamedata queries')

View File

@@ -44,7 +44,9 @@ items_table = Table("invtypes", gamedata_meta,
Column("metaLevel", Integer),
Column("metaGroupID", Integer, ForeignKey("invmetagroups.metaGroupID"), index=True),
Column("variationParentTypeID", Integer, ForeignKey("invtypes.typeID"), index=True),
Column("replacements", String))
Column("replacements", String),
Column("reqskills", String),
Column("requiredfor", String))
from .traits import traits_table # noqa

View File

@@ -424,25 +424,3 @@ def getDynamicItem(itemID, eager=None):
except exc.NoResultFound:
result = None
return result
def getRequiredFor(itemID, attrMapping):
Attribute1 = aliased(Attribute)
Attribute2 = aliased(Attribute)
skillToLevelClauses = []
for attrSkill, attrLevel in attrMapping.items():
skillToLevelClauses.append(and_(Attribute1.attributeID == attrSkill, Attribute2.attributeID == attrLevel))
queryOr = or_(*skillToLevelClauses)
q = select((Attribute2.typeID, Attribute2.value),
and_(Attribute1.value == itemID, queryOr),
from_obj=[
join(Attribute1, Attribute2, Attribute1.typeID == Attribute2.typeID)
])
result = gamedata_session.execute(q).fetchall()
return result

View File

@@ -8,43 +8,25 @@ many upgrade files as there are database versions (version 5 would include
upgrade files 1-5)
"""
import pkgutil
import re
from eos.utils.pyinst_support import iterNamespace
updates = {}
appVersion = 0
prefix = __name__ + "."
# load modules to work based with and without pyinstaller
# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
# see: https://github.com/pyinstaller/pyinstaller/issues/1905
# load modules using iter_modules()
# (should find all filters in normal build, but not pyinstaller)
module_names = [m[1] for m in pkgutil.iter_modules(__path__, prefix)]
# special handling for PyInstaller
importers = map(pkgutil.get_importer, __path__)
toc = set()
for i in importers:
if hasattr(i, 'toc'):
toc |= i.toc
for elm in toc:
if elm.startswith(prefix):
module_names.append(elm)
for modname in module_names:
for modName in iterNamespace(__name__, __path__):
# loop through python files, extracting update number and function, and
# adding it to a list
modname_tail = modname.rsplit('.', 1)[-1]
module = __import__(modname, fromlist=True)
modname_tail = modName.rsplit('.', 1)[-1]
m = re.match("^upgrade(?P<index>\d+)$", modname_tail)
if not m:
continue
index = int(m.group("index"))
appVersion = max(appVersion, index)
module = __import__(modName, fromlist=True)
upgrade = getattr(module, "upgrade", False)
if upgrade:
updates[index] = upgrade

View File

@@ -0,0 +1,166 @@
"""
Migration 35
- Remove builtin damage patterns and target profiles from the database
"""
import sqlalchemy
dmgPatterns = (
'Uniform',
'[Bombs]Concussion Bomb',
'[Bombs]Electron Bomb',
'[Bombs]Scorch Bomb',
'[Bombs]Shrapnel Bomb',
'[Exotic Plasma]Baryon',
'[Exotic Plasma]Meson',
'[Exotic Plasma]Tetryon',
'[Exotic Plasma][T2] Mystic',
'[Exotic Plasma][T2] Occult',
'[Frequency Crystals]Gamma',
'[Frequency Crystals]Infrared',
'[Frequency Crystals]Microwave',
'[Frequency Crystals]Multifrequency',
'[Frequency Crystals]Radio',
'[Frequency Crystals]Standard',
'[Frequency Crystals]Ultraviolet',
'[Frequency Crystals]Xray',
'[Frequency Crystals][T2] Aurora',
'[Frequency Crystals][T2] Conflagration',
'[Frequency Crystals][T2] Gleam',
'[Frequency Crystals][T2] Scorch',
'[Generic]EM',
'[Generic]Explosive',
'[Generic]Kinetic',
'[Generic]Thermal',
'[Hybrid Charges]Antimatter',
'[Hybrid Charges]Iridium',
'[Hybrid Charges]Iron',
'[Hybrid Charges]Lead',
'[Hybrid Charges]Plutonium',
'[Hybrid Charges]Thorium',
'[Hybrid Charges]Tungsten',
'[Hybrid Charges]Uranium',
'[Hybrid Charges][T2] Javelin',
'[Hybrid Charges][T2] Null',
'[Hybrid Charges][T2] Spike',
'[Hybrid Charges][T2] Void',
'[Missiles]Inferno',
'[Missiles]Mjolnir',
'[Missiles]Nova',
'[Missiles]Scourge',
'[Missiles][Structure) Standup Missile',
'[Missiles][Structure] Standup Missile',
'[NPC][Asteroid] Angel Cartel',
'[NPC][Asteroid] Blood Raiders',
'[NPC][Asteroid] Guristas',
'[NPC][Asteroid] Rogue Drone',
'[NPC][Asteroid] Sanshas Nation',
'[NPC][Asteroid] Serpentis',
'[NPC][Burner] Ashimmu (Blood Raiders)',
'[NPC][Burner] Cruor (Blood Raiders)',
'[NPC][Burner] Daredevil (Serpentis)',
'[NPC][Burner] Dramiel (Angel)',
'[NPC][Burner] Enyo',
'[NPC][Burner] Hawk',
'[NPC][Burner] Jaguar',
'[NPC][Burner] Sentinel',
'[NPC][Burner] Succubus (Sanshas Nation)',
'[NPC][Burner] Talos',
'[NPC][Burner] Vengeance',
'[NPC][Burner] Worm (Guristas)',
'[NPC][Deadspace] Angel Cartel',
'[NPC][Deadspace] Blood Raiders',
'[NPC][Deadspace] Guristas',
'[NPC][Deadspace] Rogue Drone',
'[NPC][Deadspace] Sanshas Nation',
'[NPC][Deadspace] Serpentis',
'[NPC][Mission] Amarr Empire',
'[NPC][Mission] CONCORD',
'[NPC][Mission] Caldari State',
'[NPC][Mission] Gallente Federation',
'[NPC][Mission] Khanid',
'[NPC][Mission] Minmatar Republic',
'[NPC][Mission] Mordus Legion',
'[NPC][Mission] Thukker',
'[NPC][Other] Sansha Incursion',
'[NPC][Other] Sleepers',
'[Projectile Ammo]Carbonized Lead',
'[Projectile Ammo]Depleted Uranium',
'[Projectile Ammo]EMP',
'[Projectile Ammo]Fusion',
'[Projectile Ammo]Nuclear',
'[Projectile Ammo]Phased Plasma',
'[Projectile Ammo]Proton',
'[Projectile Ammo]Titanium Sabot',
'[Projectile Ammo][T2] Barrage',
'[Projectile Ammo][T2] Hail',
'[Projectile Ammo][T2] Quake',
'[Projectile Ammo][T2] Tremor')
tgtProfiles = (
'Uniform (25%)',
'Uniform (50%)',
'Uniform (75%)',
'Uniform (90%)',
'[NPC][Asteroid] Angel Cartel',
'[NPC][Asteroid] Blood Raiders',
'[NPC][Asteroid] Guristas',
'[NPC][Asteroid] Rogue Drones',
'[NPC][Asteroid] Sanshas Nation',
'[NPC][Asteroid] Serpentis',
'[NPC][Burner] Ashimmu (Blood Raiders)',
'[NPC][Burner] Cruor (Blood Raiders)',
'[NPC][Burner] Daredevil (Serpentis)',
'[NPC][Burner] Dramiel (Angel)',
'[NPC][Burner] Enyo',
'[NPC][Burner] Hawk',
'[NPC][Burner] Jaguar',
'[NPC][Burner] Sentinel',
'[NPC][Burner] Succubus (Sanshas Nation)',
'[NPC][Burner] Talos',
'[NPC][Burner] Vengeance',
'[NPC][Burner] Worm (Guristas)',
'[NPC][Deadspace] Angel Cartel',
'[NPC][Deadspace] Blood Raiders',
'[NPC][Deadspace] Guristas',
'[NPC][Deadspace] Rogue Drones',
'[NPC][Deadspace] Sanshas Nation',
'[NPC][Deadspace] Serpentis',
'[NPC][Mission] Amarr Empire',
'[NPC][Mission] CONCORD',
'[NPC][Mission] Caldari State',
'[NPC][Mission] Gallente Federation',
'[NPC][Mission] Khanid',
'[NPC][Mission] Minmatar Republic',
'[NPC][Mission] Mordus Legion',
'[NPC][Other] Sansha Incursion',
'[NPC][Other] Sleeper',
'[T1 Resist]Armor',
'[T1 Resist]Armor (+T2 DCU)',
'[T1 Resist]Hull',
'[T1 Resist]Hull (+T2 DCU)',
'[T1 Resist]Shield',
'[T1 Resist]Shield (+T2 DCU)',
'[T2 Resist]Amarr (Armor)',
'[T2 Resist]Amarr (Shield)',
'[T2 Resist]Caldari (Armor)',
'[T2 Resist]Caldari (Shield)',
'[T2 Resist]Gallente (Armor)',
'[T2 Resist]Gallente (Shield)',
'[T2 Resist]Minmatar (Armor)',
'[T2 Resist]Minmatar (Shield)')
def upgrade(saveddata_engine):
saveddata_engine.execute('DELETE FROM damagePatterns WHERE name in ({});'.format(', '.join('\'{}\''.format(n) for n in dmgPatterns)))
saveddata_engine.execute('DELETE FROM targetResists WHERE name in ({});'.format(', '.join('\'{}\''.format(n) for n in tgtProfiles)))
try:
saveddata_engine.execute("SELECT builtinDamagePatternID FROM fits LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN builtinDamagePatternID INT;")
try:
saveddata_engine.execute("SELECT builtinTargetResistsID FROM fits LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN builtinTargetResistsID INT;")

View File

@@ -13,6 +13,5 @@ __all__ = [
"miscData",
"targetProfile",
"override",
"implantSet",
"loadDefaultDatabaseValues"
"implantSet"
]

View File

@@ -24,16 +24,20 @@ import datetime
from eos.db import saveddata_meta
from eos.saveddata.damagePattern import DamagePattern
damagePatterns_table = Table("damagePatterns", saveddata_meta,
Column("ID", Integer, primary_key=True),
Column("name", String),
Column("emAmount", Float),
Column("thermalAmount", Float),
Column("kineticAmount", Float),
Column("explosiveAmount", Float),
Column("ownerID", ForeignKey("users.ID"), nullable=True),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
damagePatterns_table = Table(
'damagePatterns',
saveddata_meta,
Column('ID', Integer, primary_key=True),
Column('name', String),
Column('emAmount', Float),
Column('thermalAmount', Float),
Column('kineticAmount', Float),
Column('explosiveAmount', Float),
Column('ownerID', ForeignKey('users.ID'), nullable=True),
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
mapper(DamagePattern, damagePatterns_table)
mapper(
DamagePattern,
damagePatterns_table,
properties={'rawName': damagePatterns_table.c.name})

View File

@@ -53,8 +53,10 @@ fits_table = Table("fits", saveddata_meta,
Column("timestamp", Integer, nullable=False),
Column("characterID", ForeignKey("characters.ID"), nullable=True),
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
Column("builtinDamagePatternID", Integer, nullable=True),
Column("booster", Boolean, nullable=False, index=True, default=0),
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
Column("builtinTargetResistsID", Integer, nullable=True),
Column("modeID", Integer, nullable=True),
Column("implantLocation", Integer, nullable=False),
Column("notes", String, nullable=True),
@@ -233,8 +235,10 @@ mapper(es_Fit, fits_table,
"_Fit__character": relation(
Character,
backref="fits"),
"_Fit__damagePattern": relation(DamagePattern),
"_Fit__targetProfile": relation(TargetProfile),
"_Fit__userDamagePattern": relation(DamagePattern),
"_Fit__builtinDamagePatternID": fits_table.c.builtinDamagePatternID,
"_Fit__userTargetProfile": relation(TargetProfile),
"_Fit__builtinTargetProfileID": fits_table.c.builtinTargetResistsID,
"projectedOnto": projectedFitSourceRel,
"victimOf": relationship(
ProjectedFit,

View File

@@ -1,248 +0,0 @@
# ===============================================================================
# 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 eos.db
from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern
from eos.saveddata.targetProfile import TargetProfile as es_TargetProfile
class ImportError(Exception):
pass
class DefaultDatabaseValues:
def __init__(self):
pass
instance = None
@classmethod
def importDamageProfileDefaults(cls):
damageProfileList = [["Uniform", 25, 25, 25, 25],
["[Generic]EM", 1, 0, 0, 0],
["[Generic]Thermal", 0, 1, 0, 0],
["[Generic]Kinetic", 0, 0, 1, 0],
["[Generic]Explosive", 0, 0, 0, 1],
["[NPC][Asteroid] Blood Raiders", 5067, 4214, 0, 0],
["[Bombs]Electron Bomb", 6400, 0, 0, 0],
["[Bombs]Scorch Bomb", 0, 6400, 0, 0],
["[Bombs]Concussion Bomb", 0, 0, 6400, 0],
["[Bombs]Shrapnel Bomb", 0, 0, 0, 6400],
["[Frequency Crystals][T2] Conflagration", 7.7, 7.7, 0, 0],
["[Frequency Crystals][T2] Scorch", 9, 2, 0, 0],
["[Frequency Crystals][T2] Gleam", 7, 7, 0, 0],
["[Frequency Crystals][T2] Aurora", 5, 3, 0, 0],
["[Frequency Crystals]Multifrequency", 7, 5, 0, 0],
["[Frequency Crystals]Gamma", 7, 4, 0, 0],
["[Frequency Crystals]Xray", 6, 4, 0, 0],
["[Frequency Crystals]Ultraviolet", 6, 3, 0, 0],
["[Frequency Crystals]Standard", 5, 3, 0, 0],
["[Frequency Crystals]Infrared", 5, 2, 0, 0],
["[Frequency Crystals]Microwave", 4, 2, 0, 0],
["[Frequency Crystals]Radio", 5, 0, 0, 0],
["[Hybrid Charges][T2] Void", 0, 7.7, 7.7, 0],
["[Hybrid Charges][T2] Null", 0, 6, 5, 0],
["[Hybrid Charges][T2] Javelin", 0, 8, 6, 0],
["[Hybrid Charges][T2] Spike", 0, 4, 4, 0],
["[Hybrid Charges]Antimatter", 0, 5, 7, 0],
["[Hybrid Charges]Plutonium", 0, 5, 6, 0],
["[Hybrid Charges]Uranium", 0, 4, 6, 0],
["[Hybrid Charges]Thorium", 0, 4, 5, 0],
["[Hybrid Charges]Lead", 0, 3, 5, 0],
["[Hybrid Charges]Iridium", 0, 3, 4, 0],
["[Hybrid Charges]Tungsten", 0, 2, 4, 0],
["[Hybrid Charges]Iron", 0, 2, 3, 0],
["[Missiles]Mjolnir", 1, 0, 0, 0],
["[Missiles]Inferno", 0, 1, 0, 0],
["[Missiles]Scourge", 0, 0, 1, 0],
["[Missiles]Nova", 0, 0, 0, 1],
["[Missiles][Structure] Standup Missile", 1, 1, 1, 1],
["[Projectile Ammo][T2] Hail", 0, 0, 3.3, 12.1],
["[Projectile Ammo][T2] Barrage", 0, 0, 5, 6],
["[Projectile Ammo][T2] Quake", 0, 0, 5, 9],
["[Projectile Ammo][T2] Tremor", 0, 0, 3, 5],
["[Projectile Ammo]EMP", 9, 0, 1, 2],
["[Projectile Ammo]Phased Plasma", 0, 10, 2, 0],
["[Projectile Ammo]Fusion", 0, 0, 2, 10],
["[Projectile Ammo]Depleted Uranium", 0, 3, 2, 3],
["[Projectile Ammo]Titanium Sabot", 0, 0, 6, 2],
["[Projectile Ammo]Proton", 3, 0, 2, 0],
["[Projectile Ammo]Carbonized Lead", 0, 0, 4, 1],
["[Projectile Ammo]Nuclear", 0, 0, 1, 4],
# Different sizes of plasma do different damage, the values here are
# average of proportions across sizes
["[Exotic Plasma][T2] Occult", 0, 55863, 0, 44137],
["[Exotic Plasma][T2] Mystic", 0, 66319, 0, 33681],
["[Exotic Plasma]Tetryon", 0, 69208, 0, 30792],
["[Exotic Plasma]Baryon", 0, 59737, 0, 40263],
["[Exotic Plasma]Meson", 0, 60519, 0, 39481],
["[NPC][Burner] Cruor (Blood Raiders)", 90, 90, 0, 0],
["[NPC][Burner] Dramiel (Angel)", 55, 0, 20, 96],
["[NPC][Burner] Daredevil (Serpentis)", 0, 110, 154, 0],
["[NPC][Burner] Succubus (Sanshas Nation)", 135, 30, 0, 0],
["[NPC][Burner] Worm (Guristas)", 0, 0, 228, 0],
["[NPC][Burner] Enyo", 0, 147, 147, 0],
["[NPC][Burner] Hawk", 0, 0, 247, 0],
["[NPC][Burner] Jaguar", 36, 0, 50, 182],
["[NPC][Burner] Vengeance", 232, 0, 0, 0],
["[NPC][Burner] Ashimmu (Blood Raiders)", 260, 100, 0, 0],
["[NPC][Burner] Talos", 0, 413, 413, 0],
["[NPC][Burner] Sentinel", 0, 75, 0, 90],
["[NPC][Asteroid] Angel Cartel", 1838, 562, 2215, 3838],
["[NPC][Deadspace] Angel Cartel", 369, 533, 1395, 3302],
["[NPC][Deadspace] Blood Raiders", 6040, 5052, 10, 15],
["[NPC][Asteroid] Guristas", 0, 1828, 7413, 0],
["[NPC][Deadspace] Guristas", 0, 1531, 9680, 0],
["[NPC][Asteroid] Rogue Drone", 394, 666, 1090, 1687],
["[NPC][Deadspace] Rogue Drone", 276, 1071, 1069, 871],
["[NPC][Asteroid] Sanshas Nation", 5586, 4112, 0, 0],
["[NPC][Deadspace] Sanshas Nation", 3009, 2237, 0, 0],
["[NPC][Asteroid] Serpentis", 0, 5373, 4813, 0],
["[NPC][Deadspace] Serpentis", 0, 3110, 1929, 0],
["[NPC][Mission] Amarr Empire", 4464, 3546, 97, 0],
["[NPC][Mission] Caldari State", 0, 2139, 4867, 0],
["[NPC][Mission] CONCORD", 336, 134, 212, 412],
["[NPC][Mission] Gallente Federation", 9, 3712, 2758, 0],
["[NPC][Mission] Khanid", 612, 483, 43, 6],
["[NPC][Mission] Minmatar Republic", 1024, 388, 1655, 4285],
["[NPC][Mission] Mordus Legion", 25, 262, 625, 0],
["[NPC][Mission] Thukker", 0, 52, 10, 79],
["[NPC][Other] Sleepers", 1472, 1472, 1384, 1384],
["[NPC][Other] Sansha Incursion", 1682, 1347, 3678, 3678]]
for damageProfileRow in damageProfileList:
name, em, therm, kin, exp = damageProfileRow
damageProfile = eos.db.getDamagePattern(name)
if damageProfile is None:
damageProfile = es_DamagePattern(em, therm, kin, exp)
damageProfile.name = name
eos.db.add(damageProfile)
else:
damageProfile.emAmount = em
damageProfile.thermalAmount = therm
damageProfile.kineticAmount = kin
damageProfile.explosiveAmount = exp
eos.db.commit()
@classmethod
def importTargetProfileDefaults(cls):
targetProfileList = [["Uniform (25%)", 0.25, 0.25, 0.25, 0.25],
["Uniform (50%)", 0.50, 0.50, 0.50, 0.50],
["Uniform (75%)", 0.75, 0.75, 0.75, 0.75],
["Uniform (90%)", 0.90, 0.90, 0.90, 0.90],
["[T1 Resist]Shield", 0.0, 0.20, 0.40, 0.50],
["[T1 Resist]Armor", 0.50, 0.45, 0.25, 0.10],
["[T1 Resist]Hull", 0.33, 0.33, 0.33, 0.33],
["[T1 Resist]Shield (+T2 DCU)", 0.125, 0.30, 0.475, 0.562],
["[T1 Resist]Armor (+T2 DCU)", 0.575, 0.532, 0.363, 0.235],
["[T1 Resist]Hull (+T2 DCU)", 0.598, 0.598, 0.598, 0.598],
["[T2 Resist]Amarr (Shield)", 0.0, 0.20, 0.70, 0.875],
["[T2 Resist]Amarr (Armor)", 0.50, 0.35, 0.625, 0.80],
["[T2 Resist]Caldari (Shield)", 0.20, 0.84, 0.76, 0.60],
["[T2 Resist]Caldari (Armor)", 0.50, 0.8625, 0.625, 0.10],
["[T2 Resist]Gallente (Shield)", 0.0, 0.60, 0.85, 0.50],
["[T2 Resist]Gallente (Armor)", 0.50, 0.675, 0.8375, 0.10],
["[T2 Resist]Minmatar (Shield)", 0.75, 0.60, 0.40, 0.50],
["[T2 Resist]Minmatar (Armor)", 0.90, 0.675, 0.25, 0.10],
["[NPC][Asteroid] Angel Cartel", 0.54, 0.42, 0.37, 0.32],
["[NPC][Asteroid] Blood Raiders", 0.34, 0.39, 0.45, 0.52],
["[NPC][Asteroid] Guristas", 0.55, 0.35, 0.3, 0.48],
["[NPC][Asteroid] Rogue Drones", 0.35, 0.38, 0.44, 0.49],
["[NPC][Asteroid] Sanshas Nation", 0.35, 0.4, 0.47, 0.53],
["[NPC][Asteroid] Serpentis", 0.49, 0.38, 0.29, 0.51],
["[NPC][Deadspace] Angel Cartel", 0.59, 0.48, 0.4, 0.32],
["[NPC][Deadspace] Blood Raiders", 0.31, 0.39, 0.47, 0.56],
["[NPC][Deadspace] Guristas", 0.57, 0.39, 0.31, 0.5],
["[NPC][Deadspace] Rogue Drones", 0.42, 0.42, 0.47, 0.49],
["[NPC][Deadspace] Sanshas Nation", 0.31, 0.39, 0.47, 0.56],
["[NPC][Deadspace] Serpentis", 0.49, 0.38, 0.29, 0.56],
["[NPC][Mission] Amarr Empire", 0.34, 0.38, 0.42, 0.46],
["[NPC][Mission] Caldari State", 0.51, 0.38, 0.3, 0.51],
["[NPC][Mission] CONCORD", 0.47, 0.46, 0.47, 0.47],
["[NPC][Mission] Gallente Federation", 0.51, 0.38, 0.31, 0.52],
["[NPC][Mission] Khanid", 0.51, 0.42, 0.36, 0.4],
["[NPC][Mission] Minmatar Republic", 0.51, 0.46, 0.41, 0.35],
["[NPC][Mission] Mordus Legion", 0.32, 0.48, 0.4, 0.62],
["[NPC][Other] Sleeper", 0.61, 0.61, 0.61, 0.61],
["[NPC][Other] Sansha Incursion", 0.65, 0.63, 0.64, 0.65],
["[NPC][Burner] Cruor (Blood Raiders)", 0.8, 0.73, 0.69, 0.67],
["[NPC][Burner] Dramiel (Angel)", 0.35, 0.48, 0.61, 0.68],
["[NPC][Burner] Daredevil (Serpentis)", 0.69, 0.59, 0.59, 0.43],
["[NPC][Burner] Succubus (Sanshas Nation)", 0.35, 0.48, 0.61, 0.68],
["[NPC][Burner] Worm (Guristas)", 0.48, 0.58, 0.69, 0.74],
["[NPC][Burner] Enyo", 0.58, 0.72, 0.86, 0.24],
["[NPC][Burner] Hawk", 0.3, 0.86, 0.79, 0.65],
["[NPC][Burner] Jaguar", 0.78, 0.65, 0.48, 0.56],
["[NPC][Burner] Vengeance", 0.66, 0.56, 0.75, 0.86],
["[NPC][Burner] Ashimmu (Blood Raiders)", 0.8, 0.76, 0.68, 0.7],
["[NPC][Burner] Talos", 0.68, 0.59, 0.59, 0.43],
["[NPC][Burner] Sentinel", 0.58, 0.45, 0.52, 0.66]]
for targetProfileRow in targetProfileList:
name = targetProfileRow[0]
em = targetProfileRow[1]
therm = targetProfileRow[2]
kin = targetProfileRow[3]
exp = targetProfileRow[4]
try:
maxVel = targetProfileRow[5]
except IndexError:
maxVel = None
try:
sigRad = targetProfileRow[6]
except IndexError:
sigRad = None
try:
radius = targetProfileRow[7]
except:
radius = None
targetProfile = eos.db.getTargetProfile(name)
if targetProfile is None:
targetProfile = es_TargetProfile(em, therm, kin, exp, maxVel, sigRad, radius)
targetProfile.name = name
eos.db.add(targetProfile)
else:
targetProfile.emAmount = em
targetProfile.thermalAmount = therm
targetProfile.kineticAmount = kin
targetProfile.explosiveAmount = exp
targetProfile.maxVelocity = maxVel
targetProfile.signatureRadius = sigRad
targetProfile.radius = radius
eos.db.commit()
@classmethod
def importRequiredDefaults(cls):
damageProfileList = [["Uniform", 25, 25, 25, 25]]
for damageProfileRow in damageProfileList:
name, em, therm, kin, exp = damageProfileRow
damageProfile = eos.db.getDamagePattern(name)
if damageProfile is None:
damageProfile = es_DamagePattern(em, therm, kin, exp)
damageProfile.name = name
eos.db.add(damageProfile)
else:
damageProfile.emAmount = em
damageProfile.thermalAmount = therm
damageProfile.kineticAmount = kin
damageProfile.explosiveAmount = exp
eos.db.commit()

View File

@@ -413,7 +413,7 @@ def getDamagePattern(lookfor, eager=None):
eager = processEager(eager)
with sd_lock:
pattern = saveddata_session.query(DamagePattern).options(*eager).filter(
DamagePattern.name == lookfor).first()
DamagePattern.rawName == lookfor).first()
else:
raise TypeError("Need integer or string as argument")
return pattern
@@ -434,7 +434,7 @@ def getTargetProfile(lookfor, eager=None):
eager = processEager(eager)
with sd_lock:
pattern = saveddata_session.query(TargetProfile).options(*eager).filter(
TargetProfile.name == lookfor).first()
TargetProfile.rawName == lookfor).first()
else:
raise TypeError("Need integer or string as argument")
return pattern

View File

@@ -24,23 +24,28 @@ import datetime
from eos.db import saveddata_meta
from eos.saveddata.targetProfile import TargetProfile
targetProfiles_table = Table("targetResists", saveddata_meta,
Column("ID", Integer, primary_key=True),
Column("name", String),
Column("emAmount", Float),
Column("thermalAmount", Float),
Column("kineticAmount", Float),
Column("explosiveAmount", Float),
Column("maxVelocity", Float, nullable=True),
Column("signatureRadius", Float, nullable=True),
Column("radius", Float, nullable=True),
Column("ownerID", ForeignKey("users.ID"), nullable=True),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
)
mapper(TargetProfile, targetProfiles_table,
properties={
"_maxVelocity": targetProfiles_table.c.maxVelocity,
"_signatureRadius": targetProfiles_table.c.signatureRadius,
"_radius": targetProfiles_table.c.radius})
targetProfiles_table = Table(
'targetResists',
saveddata_meta,
Column('ID', Integer, primary_key=True),
Column('name', String),
Column('emAmount', Float),
Column('thermalAmount', Float),
Column('kineticAmount', Float),
Column('explosiveAmount', Float),
Column('maxVelocity', Float, nullable=True),
Column('signatureRadius', Float, nullable=True),
Column('radius', Float, nullable=True),
Column('ownerID', ForeignKey('users.ID'), nullable=True),
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
mapper(
TargetProfile,
targetProfiles_table,
properties={
'rawName': targetProfiles_table.c.name,
'_maxVelocity': targetProfiles_table.c.maxVelocity,
'_signatureRadius': targetProfiles_table.c.signatureRadius,
'_radius': targetProfiles_table.c.radius})

View File

@@ -275,6 +275,17 @@ class Effect51(BaseEffect):
fit.ship.multiplyItemAttr('rechargeRate', module.getModifiedItemAttr('capacitorRechargeRateMultiplier'), **kwargs)
class Effect54(BaseEffect):
"""
targetPassively
Used by:
Modules from group: Passive Targeting System (6 of 6)
"""
type = 'active'
class Effect55(BaseEffect):
"""
targetHostiles
@@ -1344,7 +1355,6 @@ class Effect508(BaseEffect):
Ship: Cheetah
Ship: Freki
Ship: Republic Fleet Firetail
Ship: Rifter
Ship: Slasher
Ship: Stiletto
Ship: Wolf
@@ -1731,7 +1741,7 @@ class Effect596(BaseEffect):
ammoInfluenceRange
Used by:
Items from category: Charge (587 of 951)
Items from category: Charge (590 of 954)
"""
type = 'passive'
@@ -1746,9 +1756,10 @@ class Effect598(BaseEffect):
ammoSpeedMultiplier
Used by:
Charges from group: Festival Charges (28 of 28)
Charges from group: Festival Charges (27 of 28)
Charges from group: Interdiction Probe (2 of 2)
Items from market group: Special Edition Assets > Special Edition Festival Assets (32 of 35)
Charges from group: Structure Festival Charges (2 of 2)
Special Edition Assetss from group: Festival Charges Expired (4 of 4)
"""
type = 'passive'
@@ -1783,7 +1794,13 @@ class Effect600(BaseEffect):
ammoTrackingMultiplier
Used by:
Items from category: Charge (182 of 951)
Charges from group: Advanced Artillery Ammo (8 of 8)
Charges from group: Advanced Autocannon Ammo (8 of 8)
Charges from group: Advanced Beam Laser Crystal (8 of 8)
Charges from group: Advanced Blaster Charge (8 of 8)
Charges from group: Advanced Exotic Plasma Charge (6 of 6)
Charges from group: Advanced Pulse Laser Crystal (8 of 8)
Charges from group: Advanced Railgun Charge (8 of 8)
Charges from group: Projectile Ammo (128 of 128)
"""
@@ -2310,7 +2327,7 @@ class Effect804(BaseEffect):
ammoInfluenceCapNeed
Used by:
Items from category: Charge (493 of 951)
Items from category: Charge (496 of 954)
"""
type = 'passive'
@@ -4787,7 +4804,7 @@ class Effect1615(BaseEffect):
shipAdvancedSpaceshipCommandAgilityBonus
Used by:
Items from market group: Ships > Capital Ships (40 of 40)
Items from market group: Ships > Capital Ships (40 of 41)
"""
type = 'passive'
@@ -4820,7 +4837,7 @@ class Effect1617(BaseEffect):
shipCapitalAgilityBonus
Used by:
Items from market group: Ships > Capital Ships (31 of 40)
Items from market group: Ships > Capital Ships (31 of 41)
"""
type = 'passive'
@@ -6797,7 +6814,10 @@ class Effect2298(BaseEffect):
scanStrengthBonusPercentPassive
Used by:
Implants named like: High grade (20 of 66)
Implants named like: High grade Grail (5 of 6)
Implants named like: High grade Jackal (5 of 6)
Implants named like: High grade Spur (5 of 6)
Implants named like: High grade Talon (5 of 6)
"""
type = 'passive'
@@ -9152,7 +9172,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 888)
Items from market group: Ship Equipment > Turrets & Launchers (429 of 889)
Module: Interdiction Sphere Launcher I
"""
@@ -9215,7 +9235,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 (18 of 18)
Modules from group: Precursor Weapon (19 of 19)
Modules from group: Projectile Weapon (99 of 165)
"""
@@ -12748,8 +12768,8 @@ class Effect4038(BaseEffect):
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
fit.modules.filteredItemMultiply(lambda mod: 'overloadECMStrenghtBonus' in mod.itemModifiedAttributes,
'overloadECMStrenghtBonus', module.getModifiedItemAttr('overloadBonusMultiplier'), **kwargs)
fit.modules.filteredItemMultiply(lambda mod: 'overloadECMStrengthBonus' in mod.itemModifiedAttributes,
'overloadECMStrengthBonus', module.getModifiedItemAttr('overloadBonusMultiplier'), **kwargs)
class Effect4039(BaseEffect):
@@ -24464,7 +24484,7 @@ class Effect6104(BaseEffect):
entosisDurationMultiply
Used by:
Items from market group: Ships > Capital Ships (31 of 40)
Items from market group: Ships > Capital Ships (31 of 41)
"""
type = 'passive'
@@ -27491,12 +27511,32 @@ class Effect6439(BaseEffect):
stackingPenalties=True, **kwargs)
class Effect6440(BaseEffect):
"""
fighterAbilityAfterburner
Used by:
Fighters named like: Shadow (2 of 2)
Fighters named like: Siren (4 of 4)
"""
displayName = 'Afterburner'
grouped = True
runTime = 'late'
type = 'active'
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
module.boostItemAttr('maxVelocity', module.getModifiedItemAttr('fighterAbilityAfterburnerSpeedBonus'),
stackingPenalties=True, **kwargs)
class Effect6441(BaseEffect):
"""
fighterAbilityMicroWarpDrive
Used by:
Items from category: Fighter (48 of 82)
Items from category: Fighter (44 of 82)
"""
displayName = 'Microwarpdrive'
@@ -27893,7 +27933,7 @@ class Effect6488(BaseEffect):
Charges from group: Sensor Booster Script (3 of 3)
"""
type = 'active'
type = 'passive'
@staticmethod
def handler(fit, module, context, projectionRange, **kwargs):
@@ -29036,7 +29076,8 @@ class Effect6582(BaseEffect):
# Turrets
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Energy Turret') or
mod.item.requiresSkill('Capital Hybrid Turret') or
mod.item.requiresSkill('Capital Projectile Turret'),
mod.item.requiresSkill('Capital Projectile Turret') or
mod.item.requiresSkill('Capital Precursor Weapon'),
'damageMultiplier', src.getModifiedItemAttr('siegeTurretDamageBonus'), **kwargs)
fit.modules.filteredItemMultiply(lambda mod: mod.item.requiresSkill('Motion Prediction'),
@@ -33923,7 +33964,7 @@ class Effect6995(BaseEffect):
targetDisintegratorAttack
Used by:
Modules from group: Precursor Weapon (18 of 18)
Modules from group: Precursor Weapon (19 of 19)
"""
type = 'active'
@@ -35121,6 +35162,7 @@ class Effect7092(BaseEffect):
Ship: Hydra
Ship: Leshak
Ship: Tiamat
Ship: Zirnitra
"""
type = 'passive'
@@ -35144,6 +35186,7 @@ class Effect7093(BaseEffect):
Ship: Hydra
Ship: Leshak
Ship: Tiamat
Ship: Zirnitra
"""
type = 'passive'
@@ -35166,6 +35209,7 @@ class Effect7094(BaseEffect):
Ship: Hydra
Ship: Leshak
Ship: Tiamat
Ship: Zirnitra
"""
type = 'passive'
@@ -35222,6 +35266,7 @@ class Effect7112(BaseEffect):
Ship: Hydra
Ship: Leshak
Ship: Tiamat
Ship: Zirnitra
"""
type = 'passive'
@@ -36011,7 +36056,7 @@ class Effect7232(BaseEffect):
modifyDamageMultiplierBonusMax
Used by:
Implants named like: Grade Mimesis (10 of 12)
Implants named like: Grade Mimesis (15 of 18)
"""
type = 'passive'
@@ -36028,7 +36073,7 @@ class Effect7233(BaseEffect):
modifyDamageMultiplierBonusPerCycle
Used by:
Implants named like: Grade Mimesis (10 of 12)
Implants named like: Grade Mimesis (15 of 18)
"""
type = 'passive'
@@ -36045,7 +36090,7 @@ class Effect7234(BaseEffect):
implantSetMimesis
Used by:
Implants named like: Grade Mimesis (12 of 12)
Implants named like: Grade Mimesis (18 of 18)
"""
runTime = 'early'
@@ -36059,3 +36104,135 @@ class Effect7234(BaseEffect):
fit.appliedImplants.filteredItemMultiply(
lambda imp: imp.item.group.name == 'Cyberimplant', 'damageMultiplierBonusPerCycleModifier',
implant.getModifiedItemAttr('setBonusMimesis'), **kwargs)
class Effect7238(BaseEffect):
"""
shipBonusDreadnoughtPC1DamageMultMax
Used by:
Ship: Zirnitra
"""
type = 'passive'
@staticmethod
def handler(fit, src, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Precursor Weapon'), 'damageMultiplier',
src.getModifiedItemAttr('shipBonusDreadnoughtPC1'), skill='Precursor Dreadnought', **kwargs)
class Effect7239(BaseEffect):
"""
shipBonusDreadnoughtPC2ArmorResists
Used by:
Ship: Zirnitra
"""
type = 'passive'
@staticmethod
def handler(fit, src, context, projectionRange, **kwargs):
for type in ('Em', 'Explosive', 'Kinetic', 'Thermal'):
fit.ship.boostItemAttr('armor{0}DamageResonance'.format(type), src.getModifiedItemAttr('shipBonusDreadnoughtPC2'),
skill='Precursor Dreadnought', **kwargs)
class Effect7240(BaseEffect):
"""
shipBonusDreadnoughtPC3WeaponSpeed
Used by:
Ship: Zirnitra
"""
type = 'passive'
@staticmethod
def handler(fit, src, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Precursor Weapon'), 'speed',
src.getModifiedItemAttr('shipBonusDreadnoughtPC3'), skill='Precursor Dreadnought', **kwargs)
class Effect7242(BaseEffect):
"""
capitalPrecursorTurretDmgBonusRequiredSkill
Used by:
Skill: Capital Precursor Weapon
"""
type = 'passive'
@staticmethod
def handler(fit, container, context, projectionRange, **kwargs):
level = container.level if 'skill' in context else 1
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Capital Precursor Weapon'),
'damageMultiplier', container.getModifiedItemAttr('damageMultiplierBonus') * level, **kwargs)
class Effect7247(BaseEffect):
"""
shipBonusHAMHMLAoeVelocityMC
Used by:
Ship: Bellicose
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredChargeBoost(lambda mod: (mod.charge.requiresSkill('Heavy Missiles') or
mod.charge.requiresSkill('Heavy Assault Missiles')),
'aoeVelocity', ship.getModifiedItemAttr('shipBonusMC'),
skill='Minmatar Cruiser', **kwargs)
class Effect7248(BaseEffect):
"""
shipPBonusROFMF
Used by:
Ship: Rifter
"""
type = 'passive'
@staticmethod
def handler(fit, ship, context, projectionRange, **kwargs):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Small Projectile Turret'),
'speed', ship.getModifiedItemAttr('shipBonusMF'), skill='Minmatar Frigate', **kwargs)
class Effect8011(BaseEffect):
"""
shieldHpBonusPostPercentHpLocationShip
Used by:
Implants named like: grade Nirvana (10 of 12)
"""
type = 'passive'
@staticmethod
def handler(fit, container, context, projectionRange, **kwargs):
fit.ship.boostItemAttr('shieldCapacity', container.getModifiedItemAttr('shieldHpBonus'), **kwargs)
class Effect8013(BaseEffect):
"""
setBonusNirvana
Used by:
Implants named like: grade Nirvana (12 of 12)
"""
runTime = 'early'
type = 'passive'
@staticmethod
def handler(fit, implant, context, projectionRange, **kwargs):
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill('Cybernetics'),
'shieldHpBonus', implant.getModifiedItemAttr('ImplantSetNirvana') or 1, **kwargs)

View File

@@ -18,6 +18,7 @@
# ===============================================================================
import json
from collections import OrderedDict
from logbook import Logger
@@ -314,50 +315,26 @@ class Item(EqBase):
eos.db.saveddata_session.delete(override)
eos.db.commit()
srqIDMap = {182: 277, 183: 278, 184: 279, 1285: 1286, 1289: 1287, 1290: 1288}
@property
def requiredSkills(self):
if self.__requiredSkills is None:
requiredSkills = OrderedDict()
self.__requiredSkills = requiredSkills
# Map containing attribute IDs we may need for required skills
# { requiredSkillX : requiredSkillXLevel }
combinedAttrIDs = set(self.srqIDMap.keys()).union(set(self.srqIDMap.values()))
# Map containing result of the request
# { attributeID : attributeValue }
skillAttrs = {}
# Get relevant attribute values from db (required skill IDs and levels) for our item
for attrInfo in eos.db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)):
attrID = attrInfo[1]
attrVal = attrInfo[2]
skillAttrs[attrID] = attrVal
# Go through all attributeID pairs
for srqIDAtrr, srqLvlAttr in self.srqIDMap.items():
# Check if we have both in returned result
if srqIDAtrr in skillAttrs and srqLvlAttr in skillAttrs:
skillID = int(skillAttrs[srqIDAtrr])
skillLvl = skillAttrs[srqLvlAttr]
# Fetch item from database and fill map
item = eos.db.getItem(skillID)
requiredSkills[item] = skillLvl
self.__requiredSkills = {}
if self.reqskills:
for skillTypeID, skillLevel in json.loads(self.reqskills).items():
skillItem = eos.db.getItem(int(skillTypeID))
if skillItem:
self.__requiredSkills[skillItem] = skillLevel
return self.__requiredSkills
@property
def requiredFor(self):
if self.__requiredFor is None:
self.__requiredFor = dict()
# Map containing attribute IDs we may need for required skills
# Get relevant attribute values from db (required skill IDs and levels) for our item
q = eos.db.getRequiredFor(self.ID, self.srqIDMap)
for itemID, lvl in q:
# Fetch item from database and fill map
item = eos.db.getItem(itemID)
self.__requiredFor[item] = lvl
self.__requiredFor = {}
if self.requiredfor:
for typeID, skillLevel in json.loads(self.requiredfor).items():
requiredForItem = eos.db.getItem(int(typeID))
if requiredForItem:
self.__requiredFor[requiredForItem] = skillLevel
return self.__requiredFor
factionMap = {
@@ -373,7 +350,8 @@ class Item(EqBase):
500016: "sisters",
500018: "mordu",
500019: "sansha",
500020: "serpentis"
500020: "serpentis",
500026: "triglavian"
}
@property
@@ -399,6 +377,7 @@ class Item(EqBase):
9 : "guristas", # Caldari + Gallente
10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills
12 : "sisters", # Amarr + Gallente
15 : "concord",
16 : "jove",
32 : "sansha", # Incrusion Sansha
128: "ore",

View File

@@ -18,21 +18,174 @@
# ===============================================================================
import re
from collections import OrderedDict
from sqlalchemy.orm import reconstructor
import eos.db
# Order is significant here - UI uses order as-is for built-in patterns
BUILTINS = OrderedDict([
(-1, ('Uniform', 25, 25, 25, 25)),
(-2, ('[Generic]EM', 1, 0, 0, 0)),
(-3, ('[Generic]Thermal', 0, 1, 0, 0)),
(-4, ('[Generic]Kinetic', 0, 0, 1, 0)),
(-5, ('[Generic]Explosive', 0, 0, 0, 1)),
(-6, ('[Frequency Crystals]|[T2] Aurora', 5, 3, 0, 0)),
(-7, ('[Frequency Crystals]|[T2] Scorch', 9, 2, 0, 0)),
(-8, ('[Frequency Crystals]Radio', 5, 0, 0, 0)),
(-9, ('[Frequency Crystals]Microwave', 4, 2, 0, 0)),
(-10, ('[Frequency Crystals]Infrared', 5, 2, 0, 0)),
(-11, ('[Frequency Crystals]Standard', 5, 3, 0, 0)),
(-12, ('[Frequency Crystals]Ultraviolet', 6, 3, 0, 0)),
(-13, ('[Frequency Crystals]Xray', 6, 4, 0, 0)),
(-14, ('[Frequency Crystals]Gamma', 7, 4, 0, 0)),
(-15, ('[Frequency Crystals]Multifrequency', 7, 5, 0, 0)),
(-16, ('[Frequency Crystals]|[T2] Gleam', 7, 7, 0, 0)),
(-17, ('[Frequency Crystals]|[T2] Conflagration', 7.7, 7.7, 0, 0)),
# Different sizes of plasma do different damage ratios, the values here
# are average of ratios across sizes
(-18, ('[Exotic Plasma]|[T2] Mystic', 0, 66319, 0, 33681)),
(-19, ('[Exotic Plasma]Meson', 0, 60519, 0, 39481)),
(-20, ('[Exotic Plasma]Baryon', 0, 59737, 0, 40263)),
(-21, ('[Exotic Plasma]Tetryon', 0, 69208, 0, 30792)),
(-22, ('[Exotic Plasma]|[T2] Occult', 0, 55863, 0, 44137)),
(-23, ('[Hybrid Charges]|[T2] Spike', 0, 4, 4, 0)),
(-24, ('[Hybrid Charges]|[T2] Null', 0, 6, 5, 0)),
(-25, ('[Hybrid Charges]Iron', 0, 2, 3, 0)),
(-26, ('[Hybrid Charges]Tungsten', 0, 2, 4, 0)),
(-27, ('[Hybrid Charges]Iridium', 0, 3, 4, 0)),
(-28, ('[Hybrid Charges]Lead', 0, 3, 5, 0)),
(-29, ('[Hybrid Charges]Thorium', 0, 4, 5, 0)),
(-30, ('[Hybrid Charges]Uranium', 0, 4, 6, 0)),
(-31, ('[Hybrid Charges]Plutonium', 0, 5, 6, 0)),
(-32, ('[Hybrid Charges]Antimatter', 0, 5, 7, 0)),
(-33, ('[Hybrid Charges]|[T2] Javelin', 0, 8, 6, 0)),
(-34, ('[Hybrid Charges]|[T2] Void', 0, 7.7, 7.7, 0)),
(-35, ('[Projectile Ammo]|[T2] Tremor', 0, 0, 3, 5)),
(-36, ('[Projectile Ammo]|[T2] Barrage', 0, 0, 5, 6)),
(-37, ('[Projectile Ammo]Carbonized Lead', 0, 0, 4, 1)),
(-38, ('[Projectile Ammo]Nuclear', 0, 0, 1, 4)),
(-39, ('[Projectile Ammo]Proton', 3, 0, 2, 0)),
(-40, ('[Projectile Ammo]Depleted Uranium', 0, 3, 2, 3)),
(-41, ('[Projectile Ammo]Titanium Sabot', 0, 0, 6, 2)),
(-42, ('[Projectile Ammo]EMP', 9, 0, 1, 2)),
(-43, ('[Projectile Ammo]Phased Plasma', 0, 10, 2, 0)),
(-44, ('[Projectile Ammo]Fusion', 0, 0, 2, 10)),
(-45, ('[Projectile Ammo]|[T2] Quake', 0, 0, 5, 9)),
(-46, ('[Projectile Ammo]|[T2] Hail', 0, 0, 3.3, 12.1)),
(-47, ('[Missiles]Mjolnir', 1, 0, 0, 0)),
(-48, ('[Missiles]Inferno', 0, 1, 0, 0)),
(-49, ('[Missiles]Scourge', 0, 0, 1, 0)),
(-50, ('[Missiles]Nova', 0, 0, 0, 1)),
(-51, ('[Bombs]Electron Bomb', 6400, 0, 0, 0)),
(-52, ('[Bombs]Scorch Bomb', 0, 6400, 0, 0)),
(-53, ('[Bombs]Concussion Bomb', 0, 0, 6400, 0)),
(-54, ('[Bombs]Shrapnel Bomb', 0, 0, 0, 6400)),
# Source: ticket #2067
(-55, ('[NPC][Abyssal]All', 130, 396, 258, 216)),
(-56, ('[NPC][Abyssal]Drifter', 250, 250, 250, 250)),
(-57, ('[NPC][Abyssal]Drones', 250, 250, 250, 250)),
(-58, ('[NPC][Abyssal]Overmind', 0, 408, 592, 0)),
(-59, ('[NPC][Abyssal]Seeker', 406, 406, 94, 94)),
(-60, ('[NPC][Abyssal]Sleeper', 313, 313, 187, 187)),
(-61, ('[NPC][Abyssal]Triglavian', 0, 610, 0, 390)),
(-62, ('[NPC][Asteroid]Angel Cartel', 1838, 562, 2215, 3838)),
(-63, ('[NPC][Asteroid]Blood Raiders', 5067, 4214, 0, 0)),
(-64, ('[NPC][Asteroid]Guristas', 0, 1828, 7413, 0)),
(-65, ('[NPC][Asteroid]Rogue Drone', 394, 666, 1090, 1687)),
(-66, ('[NPC][Asteroid]Sanshas Nation', 5586, 4112, 0, 0)),
(-67, ('[NPC][Asteroid]Serpentis', 0, 5373, 4813, 0)),
(-68, ('[NPC][Burner]Cruor (Blood Raiders)', 90, 90, 0, 0)),
(-69, ('[NPC][Burner]Dramiel (Angel)', 55, 0, 20, 96)),
(-70, ('[NPC][Burner]Daredevil (Serpentis)', 0, 110, 154, 0)),
(-71, ('[NPC][Burner]Succubus (Sanshas Nation)', 135, 30, 0, 0)),
(-72, ('[NPC][Burner]Worm (Guristas)', 0, 0, 228, 0)),
(-73, ('[NPC][Burner]Enyo', 0, 147, 147, 0)),
(-74, ('[NPC][Burner]Hawk', 0, 0, 247, 0)),
(-75, ('[NPC][Burner]Jaguar', 36, 0, 50, 182)),
(-76, ('[NPC][Burner]Vengeance', 232, 0, 0, 0)),
(-77, ('[NPC][Burner]Ashimmu (Blood Raiders)', 260, 100, 0, 0)),
(-78, ('[NPC][Burner]Talos', 0, 413, 413, 0)),
(-79, ('[NPC][Burner]Sentinel', 0, 75, 0, 90)),
(-80, ('[NPC][Deadspace]Angel Cartel', 369, 533, 1395, 3302)),
(-81, ('[NPC][Deadspace]Blood Raiders', 6040, 5052, 10, 15)),
(-82, ('[NPC][Deadspace]Guristas', 0, 1531, 9680, 0)),
(-83, ('[NPC][Deadspace]Rogue Drone', 276, 1071, 1069, 871)),
(-84, ('[NPC][Deadspace]Sanshas Nation', 3009, 2237, 0, 0)),
(-85, ('[NPC][Deadspace]Serpentis', 0, 3110, 1929, 0)),
# Source: ticket #2067
(-86, ('[NPC][Invasion][Invading Precursor Entities]Dread', 0, 417, 0, 583)),
(-87, ('[NPC][Invasion][Invading Precursor Entities]Normal Subcaps', 0, 610, 0, 390)),
(-88, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 0% spool up', 367, 155, 367, 112)),
(-89, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 50% spool up', 291, 243, 291, 175)),
(-90, ('[NPC][Invasion][Invading Precursor Entities]Subcaps w/missiles 100% spool up', 241, 301, 241, 217)),
(-91, ('[NPC][Invasion][Retaliating Amarr Entities]Dread/Subcaps', 583, 417, 0, 0)),
(-92, ('[NPC][Invasion][Retaliating Caldari Entities]Dread', 1000, 0, 0, 0)),
(-93, ('[NPC][Invasion][Retaliating Caldari Entities]Subcaps', 511, 21, 29, 440)),
(-94, ('[NPC][Invasion][Retaliating Gallente Entities]Dread/Subcaps', 0, 417, 583, 0)),
(-95, ('[NPC][Invasion][Retaliating Minmatar Entities]Dread', 0, 0, 583, 417)),
(-96, ('[NPC][Invasion][Retaliating Minmatar Entities]Subcaps', 302, 136, 328, 234)),
(-97, ('[NPC][Mission]Amarr Empire', 4464, 3546, 97, 0)),
(-98, ('[NPC][Mission]Caldari State', 0, 2139, 4867, 0)),
(-99, ('[NPC][Mission]CONCORD', 336, 134, 212, 412)),
(-100, ('[NPC][Mission]Gallente Federation', 9, 3712, 2758, 0)),
(-101, ('[NPC][Mission]Khanid', 612, 483, 43, 6)),
(-102, ('[NPC][Mission]Minmatar Republic', 1024, 388, 1655, 4285)),
(-103, ('[NPC][Mission]Mordus Legion', 25, 262, 625, 0)),
(-104, ('[NPC][Mission]Thukker', 0, 52, 10, 79)),
(-105, ('[NPC]Sansha Incursion', 1682, 1347, 3678, 3678)),
(-106, ('[NPC]Sleepers', 1472, 1472, 1384, 1384))])
class DamagePattern:
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
_builtins = None
def __init__(self, *args, **kwargs):
self.builtin = False
self.update(*args, **kwargs)
@reconstructor
def init(self):
self.builtin = False
def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
self.kineticAmount = kineticAmount
self.explosiveAmount = explosiveAmount
@classmethod
def getBuiltinList(cls):
if cls._builtins is None:
cls.__generateBuiltins()
return list(cls._builtins.values())
@classmethod
def getBuiltinById(cls, id):
if cls._builtins is None:
cls.__generateBuiltins()
return cls._builtins.get(id)
@classmethod
def getDefaultBuiltin(cls):
if cls._builtins is None:
cls.__generateBuiltins()
return cls._builtins.get(-1)
@classmethod
def __generateBuiltins(cls):
cls._builtins = OrderedDict()
for id, (rawName, em, therm, kin, explo) in BUILTINS.items():
pattern = DamagePattern(emAmount=em, thermalAmount=therm, kineticAmount=kin, explosiveAmount=explo)
pattern.ID = id
pattern.rawName = rawName
pattern.builtin = True
cls._builtins[id] = pattern
def calculateEhp(self, fit):
ehp = {}
for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
@@ -98,7 +251,7 @@ class DamagePattern:
lookup = {}
current = eos.db.getDamagePatternList()
for pattern in current:
lookup[pattern.name] = pattern
lookup[pattern.rawName] = pattern
for line in lines:
try:
@@ -131,7 +284,7 @@ class DamagePattern:
eos.db.save(pattern)
else:
pattern = DamagePattern(**fields)
pattern.name = name.strip()
pattern.rawName = name.strip()
eos.db.save(pattern)
patterns.append(pattern)
@@ -147,11 +300,41 @@ class DamagePattern:
out += "# Values are in following format:\n"
out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
for dp in patterns:
out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
out += cls.EXPORT_FORMAT % (dp.rawName, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
return out.strip()
@property
def name(self):
return self.rawName
@property
def fullName(self):
categories, tail = self.__parseRawName()
return '{}{}'.format(''.join('[{}]'.format(c) for c in categories), tail)
@property
def shortName(self):
return self.__parseRawName()[1]
@property
def hierarchy(self):
return self.__parseRawName()[0]
def __parseRawName(self):
categories = []
remainingName = self.rawName.strip() if self.rawName else ''
while True:
start, end = remainingName.find('['), remainingName.find(']')
if start == -1 or end == -1:
return categories, remainingName
splitter = remainingName.find('|')
if splitter != -1 and splitter == start - 1:
return categories, remainingName[1:]
categories.append(remainingName[start + 1:end])
remainingName = remainingName[end + 1:].strip()
def __deepcopy__(self, memo):
p = DamagePattern(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
p.name = "%s copy" % self.name
p.rawName = "%s copy" % self.rawName
return p

View File

@@ -99,7 +99,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__chargeModifiedAttributes = ModifiedAttributeDict()
if len(self.abilities) != len(self.item.effects):
if {a.effectID for a in self.abilities} != {e.ID for e in self.item.effects.values()}:
self.__abilities = []
for ability in self.__getAbilities():
self.__abilities.append(ability)

View File

@@ -28,15 +28,17 @@ from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos import capSim
from eos.calc import calculateMultiplier, calculateLockTime
from eos.calc import calculateLockTime, calculateMultiplier
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.saveddata.damagePattern import DamagePattern
from eos.saveddata.module import Module
from eos.saveddata.ship import Ship
from eos.saveddata.targetProfile import TargetProfile
from eos.utils.stats import DmgTypes, RRTypes
@@ -163,14 +165,29 @@ class Fit:
self.__capUsed = None
self.__capRecharge = None
self.__savedCapSimData.clear()
# Ancillary tank modules affect this
self.__sustainableTank = None
self.__effectiveSustainableTank = None
@property
def targetProfile(self):
return self.__targetProfile
if self.__userTargetProfile is not None:
return self.__userTargetProfile
if self.__builtinTargetProfileID is not None:
return TargetProfile.getBuiltinById(self.__builtinTargetProfileID)
return None
@targetProfile.setter
def targetProfile(self, targetProfile):
self.__targetProfile = targetProfile
if targetProfile is None:
self.__userTargetProfile = None
self.__builtinTargetProfileID = None
elif targetProfile.builtin:
self.__userTargetProfile = None
self.__builtinTargetProfileID = targetProfile.ID
else:
self.__userTargetProfile = targetProfile
self.__builtinTargetProfileID = None
self.__weaponDpsMap = {}
self.__weaponVolleyMap = {}
self.__droneDps = None
@@ -178,11 +195,25 @@ class Fit:
@property
def damagePattern(self):
return self.__damagePattern
if self.__userDamagePattern is not None:
return self.__userDamagePattern
if self.__builtinDamagePatternID is not None:
pattern = DamagePattern.getBuiltinById(self.__builtinDamagePatternID)
if pattern is not None:
return pattern
return DamagePattern.getDefaultBuiltin()
@damagePattern.setter
def damagePattern(self, damagePattern):
self.__damagePattern = damagePattern
if damagePattern is None:
self.__userDamagePattern = None
self.__builtinDamagePatternID = None
elif damagePattern.builtin:
self.__userDamagePattern = None
self.__builtinDamagePatternID = damagePattern.ID
else:
self.__userDamagePattern = damagePattern
self.__builtinDamagePatternID = None
self.__ehp = None
self.__effectiveTank = None

View File

@@ -19,8 +19,10 @@
import math
import re
from collections import OrderedDict
from logbook import Logger
from sqlalchemy.orm import reconstructor
import eos.db
@@ -28,13 +30,173 @@ import eos.db
pyfalog = Logger(__name__)
BUILTINS = OrderedDict([
# 0 is taken by ideal target profile, composed manually in one of TargetProfile methods
(-1, ('Uniform (25%)', 0.25, 0.25, 0.25, 0.25)),
(-2, ('Uniform (50%)', 0.50, 0.50, 0.50, 0.50)),
(-3, ('Uniform (75%)', 0.75, 0.75, 0.75, 0.75)),
(-4, ('Uniform (90%)', 0.90, 0.90, 0.90, 0.90)),
(-5, ('[T1 Resist]Shield', 0.0, 0.20, 0.40, 0.50)),
(-6, ('[T1 Resist]Armor', 0.50, 0.45, 0.25, 0.10)),
(-7, ('[T1 Resist]Hull', 0.33, 0.33, 0.33, 0.33)),
(-8, ('[T1 Resist]Shield (+T2 DCU)', 0.125, 0.30, 0.475, 0.562)),
(-9, ('[T1 Resist]Armor (+T2 DCU)', 0.575, 0.532, 0.363, 0.235)),
(-10, ('[T1 Resist]Hull (+T2 DCU)', 0.598, 0.598, 0.598, 0.598)),
(-11, ('[T2 Resist]Amarr (Shield)', 0.0, 0.20, 0.70, 0.875)),
(-12, ('[T2 Resist]Amarr (Armor)', 0.50, 0.35, 0.625, 0.80)),
(-13, ('[T2 Resist]Caldari (Shield)', 0.20, 0.84, 0.76, 0.60)),
(-14, ('[T2 Resist]Caldari (Armor)', 0.50, 0.8625, 0.625, 0.10)),
(-15, ('[T2 Resist]Gallente (Shield)', 0.0, 0.60, 0.85, 0.50)),
(-16, ('[T2 Resist]Gallente (Armor)', 0.50, 0.675, 0.8375, 0.10)),
(-17, ('[T2 Resist]Minmatar (Shield)', 0.75, 0.60, 0.40, 0.50)),
(-18, ('[T2 Resist]Minmatar (Armor)', 0.90, 0.675, 0.25, 0.10)),
(-19, ('[NPC][Asteroid]Angel Cartel', 0.54, 0.42, 0.37, 0.32)),
(-20, ('[NPC][Asteroid]Blood Raiders', 0.34, 0.39, 0.45, 0.52)),
(-21, ('[NPC][Asteroid]Guristas', 0.55, 0.35, 0.3, 0.48)),
(-22, ('[NPC][Asteroid]Rogue Drones', 0.35, 0.38, 0.44, 0.49)),
(-23, ('[NPC][Asteroid]Sanshas Nation', 0.35, 0.4, 0.47, 0.53)),
(-24, ('[NPC][Asteroid]Serpentis', 0.49, 0.38, 0.29, 0.51)),
(-25, ('[NPC][Deadspace]Angel Cartel', 0.59, 0.48, 0.4, 0.32)),
(-26, ('[NPC][Deadspace]Blood Raiders', 0.31, 0.39, 0.47, 0.56)),
(-27, ('[NPC][Deadspace]Guristas', 0.57, 0.39, 0.31, 0.5)),
(-28, ('[NPC][Deadspace]Rogue Drones', 0.42, 0.42, 0.47, 0.49)),
(-29, ('[NPC][Deadspace]Sanshas Nation', 0.31, 0.39, 0.47, 0.56)),
(-30, ('[NPC][Deadspace]Serpentis', 0.49, 0.38, 0.29, 0.56)),
(-31, ('[NPC][Mission]Amarr Empire', 0.34, 0.38, 0.42, 0.46)),
(-32, ('[NPC][Mission]Caldari State', 0.51, 0.38, 0.3, 0.51)),
(-33, ('[NPC][Mission]CONCORD', 0.47, 0.46, 0.47, 0.47)),
(-34, ('[NPC][Mission]Gallente Federation', 0.51, 0.38, 0.31, 0.52)),
(-35, ('[NPC][Mission]Khanid', 0.51, 0.42, 0.36, 0.4)),
(-36, ('[NPC][Mission]Minmatar Republic', 0.51, 0.46, 0.41, 0.35)),
(-37, ('[NPC][Mission]Mordus Legion', 0.32, 0.48, 0.4, 0.62)),
(-38, ('[NPC][Other]Sleeper', 0.61, 0.61, 0.61, 0.61)),
(-39, ('[NPC][Other]Sansha Incursion', 0.65, 0.63, 0.64, 0.65)),
(-40, ('[NPC][Burner]Cruor (Blood Raiders)', 0.8, 0.73, 0.69, 0.67)),
(-41, ('[NPC][Burner]Dramiel (Angel)', 0.35, 0.48, 0.61, 0.68)),
(-42, ('[NPC][Burner]Daredevil (Serpentis)', 0.69, 0.59, 0.59, 0.43)),
(-43, ('[NPC][Burner]Succubus (Sanshas Nation)', 0.35, 0.48, 0.61, 0.68)),
(-44, ('[NPC][Burner]Worm (Guristas)', 0.48, 0.58, 0.69, 0.74)),
(-45, ('[NPC][Burner]Enyo', 0.58, 0.72, 0.86, 0.24)),
(-46, ('[NPC][Burner]Hawk', 0.3, 0.86, 0.79, 0.65)),
(-47, ('[NPC][Burner]Jaguar', 0.78, 0.65, 0.48, 0.56)),
(-48, ('[NPC][Burner]Vengeance', 0.66, 0.56, 0.75, 0.86)),
(-49, ('[NPC][Burner]Ashimmu (Blood Raiders)', 0.8, 0.76, 0.68, 0.7)),
(-50, ('[NPC][Burner]Talos', 0.68, 0.59, 0.59, 0.43)),
(-51, ('[NPC][Burner]Sentinel', 0.58, 0.45, 0.52, 0.66)),
# Source: ticket #2067
(-52, ('[NPC][Invasion]Invading Precursor Entities', 0.422, 0.367, 0.453, 0.411)),
(-53, ('[NPC][Invasion]Retaliating Amarr Entities', 0.360, 0.310, 0.441, 0.602)),
(-54, ('[NPC][Invasion]Retaliating Caldari Entities', 0.287, 0.610, 0.487, 0.401)),
(-55, ('[NPC][Invasion]Retaliating Gallente Entities', 0.383, 0.414, 0.578, 0.513)),
(-56, ('[NPC][Invasion]Retaliating Minmatar Entities', 0.620, 0.422, 0.355, 0.399)),
(-57, ('[NPC][Abyssal][Dark Matter All Tiers]Drones', 0.439, 0.522, 0.529, 0.435)),
(-58, ('[NPC][Abyssal][Dark Matter All Tiers]Overmind', 0.626, 0.576, 0.612, 0.624)),
(-59, ('[NPC][Abyssal][Dark Matter All Tiers]Seeker', 0.082, 0.082, 0.082, 0.082)),
(-60, ('[NPC][Abyssal][Dark Matter All Tiers]Triglavian', 0.477, 0.401, 0.449, 0.37)),
(-61, ('[NPC][Abyssal][Dark Matter All Tiers]Drifter', 0.403, 0.403, 0.403, 0.403)),
(-62, ('[NPC][Abyssal][Dark Matter All Tiers]Sleeper', 0.435, 0.435, 0.435, 0.435)),
(-63, ('[NPC][Abyssal][Dark Matter All Tiers]All', 0.507, 0.477, 0.502, 0.493)),
(-64, ('[NPC][Abyssal][Electrical T1/T2]Drones', 0.323, 0.522, 0.529, 0.435)),
(-65, ('[NPC][Abyssal][Electrical T1/T2]Overmind', 0.521, 0.576, 0.612, 0.624)),
(-66, ('[NPC][Abyssal][Electrical T1/T2]Seeker', 0, 0.082, 0.082, 0.082)),
(-67, ('[NPC][Abyssal][Electrical T1/T2]Triglavian', 0.333, 0.401, 0.449, 0.37)),
(-68, ('[NPC][Abyssal][Electrical T1/T2]Drifter', 0.267, 0.403, 0.403, 0.403)),
(-69, ('[NPC][Abyssal][Electrical T1/T2]Sleeper', 0.329, 0.435, 0.435, 0.435)),
(-70, ('[NPC][Abyssal][Electrical T1/T2]All', 0.385, 0.477, 0.502, 0.493)),
(-71, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drones', 0.255, 0.522, 0.529, 0.435)),
(-72, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Overmind', 0.457, 0.576, 0.612, 0.624)),
(-73, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Seeker', 0, 0.082, 0.082, 0.082)),
(-74, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Triglavian', 0.241, 0.401, 0.449, 0.37)),
(-75, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Drifter', 0.184, 0.403, 0.403, 0.403)),
(-76, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]Sleeper', 0.268, 0.435, 0.435, 0.435)),
(-77, ('[NPC][Abyssal][Electrical T3 (Some T5 Rooms)]All', 0.313, 0.477, 0.502, 0.493)),
(-78, ('[NPC][Abyssal][Electrical T4/T5]Drones', 0.193, 0.522, 0.529, 0.435)),
(-79, ('[NPC][Abyssal][Electrical T4/T5]Overmind', 0.398, 0.576, 0.612, 0.624)),
(-80, ('[NPC][Abyssal][Electrical T4/T5]Seeker', 0, 0.082, 0.082, 0.082)),
(-81, ('[NPC][Abyssal][Electrical T4/T5]Triglavian', 0.183, 0.401, 0.449, 0.37)),
(-82, ('[NPC][Abyssal][Electrical T4/T5]Drifter', 0.107, 0.403, 0.403, 0.403)),
(-83, ('[NPC][Abyssal][Electrical T4/T5]Sleeper', 0.215, 0.435, 0.435, 0.435)),
(-84, ('[NPC][Abyssal][Electrical T4/T5]All', 0.25, 0.477, 0.502, 0.493)),
(-85, ('[NPC][Abyssal][Firestorm T1/T2]Drones', 0.461, 0.425, 0.541, 0.443)),
(-86, ('[NPC][Abyssal][Firestorm T1/T2]Overmind', 0.65, 0.469, 0.625, 0.633)),
(-87, ('[NPC][Abyssal][Firestorm T1/T2]Seeker', 0.084, 0, 0.084, 0.084)),
(-88, ('[NPC][Abyssal][Firestorm T1/T2]Triglavian', 0.534, 0.266, 0.484, 0.366)),
(-89, ('[NPC][Abyssal][Firestorm T1/T2]Drifter', 0.422, 0.282, 0.422, 0.422)),
(-90, ('[NPC][Abyssal][Firestorm T1/T2]Sleeper', 0.512, 0.402, 0.512, 0.512)),
(-91, ('[NPC][Abyssal][Firestorm T1/T2]All', 0.541, 0.365, 0.524, 0.504)),
(-92, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drones', 0.461, 0.36, 0.541, 0.443)),
(-93, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Overmind', 0.65, 0.391, 0.625, 0.633)),
(-94, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Seeker', 0.084, 0, 0.084, 0.084)),
(-95, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Triglavian', 0.534, 0.161, 0.484, 0.366)),
(-96, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Drifter', 0.422, 0.196, 0.422, 0.422)),
(-97, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]Sleeper', 0.512, 0.337, 0.512, 0.512)),
(-98, ('[NPC][Abyssal][Firestorm T3 (Some T5 Rooms)]All', 0.541, 0.284, 0.524, 0.504)),
(-99, ('[NPC][Abyssal][Firestorm T4/T5]Drones', 0.461, 0.305, 0.541, 0.443)),
(-100, ('[NPC][Abyssal][Firestorm T4/T5]Overmind', 0.65, 0.323, 0.625, 0.633)),
(-101, ('[NPC][Abyssal][Firestorm T4/T5]Seeker', 0.084, 0, 0.084, 0.084)),
(-102, ('[NPC][Abyssal][Firestorm T4/T5]Triglavian', 0.534, 0.082, 0.484, 0.366)),
(-103, ('[NPC][Abyssal][Firestorm T4/T5]Drifter', 0.422, 0.114, 0.422, 0.422)),
(-104, ('[NPC][Abyssal][Firestorm T4/T5]Sleeper', 0.512, 0.276, 0.512, 0.512)),
(-105, ('[NPC][Abyssal][Firestorm T4/T5]All', 0.541, 0.214, 0.524, 0.504)),
(-106, ('[NPC][Abyssal][Exotic T1/T2]Drones', 0.439, 0.522, 0.417, 0.435)),
(-107, ('[NPC][Abyssal][Exotic T1/T2]Overmind', 0.626, 0.576, 0.496, 0.624)),
(-108, ('[NPC][Abyssal][Exotic T1/T2]Seeker', 0.082, 0.082, 0, 0.082)),
(-109, ('[NPC][Abyssal][Exotic T1/T2]Triglavian', 0.477, 0.401, 0.284, 0.37)),
(-110, ('[NPC][Abyssal][Exotic T1/T2]Drifter', 0.403, 0.403, 0.267, 0.403)),
(-111, ('[NPC][Abyssal][Exotic T1/T2]Sleeper', 0.435, 0.435, 0.329, 0.435)),
(-112, ('[NPC][Abyssal][Exotic T1/T2]All', 0.507, 0.477, 0.373, 0.493)),
(-113, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drones', 0.439, 0.522, 0.351, 0.435)),
(-114, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Overmind', 0.626, 0.576, 0.419, 0.624)),
(-115, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Seeker', 0.082, 0.082, 0, 0.082)),
(-116, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Triglavian', 0.477, 0.401, 0.176, 0.37)),
(-117, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Drifter', 0.403, 0.403, 0.184, 0.403)),
(-118, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.268, 0.435)),
(-119, ('[NPC][Abyssal][Exotic T3 (Some T5 Rooms)]All', 0.507, 0.477, 0.293, 0.493)),
(-120, ('[NPC][Abyssal][Exotic T4/T5]Drones', 0.439, 0.522, 0.293, 0.435)),
(-121, ('[NPC][Abyssal][Exotic T4/T5]Overmind', 0.626, 0.576, 0.344, 0.624)),
(-122, ('[NPC][Abyssal][Exotic T4/T5]Seeker', 0.082, 0.082, 0, 0.082)),
(-123, ('[NPC][Abyssal][Exotic T4/T5]Triglavian', 0.477, 0.401, 0.107, 0.37)),
(-124, ('[NPC][Abyssal][Exotic T4/T5]Drifter', 0.403, 0.403, 0.107, 0.403)),
(-125, ('[NPC][Abyssal][Exotic T4/T5]Sleeper', 0.435, 0.435, 0.215, 0.435)),
(-126, ('[NPC][Abyssal][Exotic T4/T5]All', 0.507, 0.477, 0.223, 0.493)),
(-127, ('[NPC][Abyssal][Gamma T1/T2]Drones', 0.449, 0.54, 0.549, 0.336)),
(-128, ('[NPC][Abyssal][Gamma T1/T2]Overmind', 0.6, 0.557, 0.601, 0.504)),
(-129, ('[NPC][Abyssal][Gamma T1/T2]Seeker', 0.085, 0.085, 0.085, 0)),
(-130, ('[NPC][Abyssal][Gamma T1/T2]Triglavian', 0.463, 0.392, 0.447, 0.193)),
(-131, ('[NPC][Abyssal][Gamma T1/T2]Drifter', 0.428, 0.428, 0.428, 0.287)),
(-132, ('[NPC][Abyssal][Gamma T1/T2]Sleeper', 0.435, 0.435, 0.435, 0.329)),
(-133, ('[NPC][Abyssal][Gamma T1/T2]All', 0.493, 0.472, 0.5, 0.362)),
(-134, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drones', 0.449, 0.54, 0.549, 0.264)),
(-135, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Overmind', 0.6, 0.557, 0.601, 0.428)),
(-136, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Seeker', 0.085, 0.085, 0.085, 0)),
(-137, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Triglavian', 0.463, 0.392, 0.447, 0.071)),
(-138, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Drifter', 0.428, 0.428, 0.428, 0.2)),
(-139, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]Sleeper', 0.435, 0.435, 0.435, 0.268)),
(-140, ('[NPC][Abyssal][Gamma T3 (Some T5 Rooms)]All', 0.493, 0.472, 0.5, 0.28)),
(-141, ('[NPC][Abyssal][Gamma T4/T5]Drones', 0.449, 0.54, 0.549, 0.197)),
(-142, ('[NPC][Abyssal][Gamma T4/T5]Overmind', 0.6, 0.557, 0.601, 0.356)),
(-143, ('[NPC][Abyssal][Gamma T4/T5]Seeker', 0.085, 0.085, 0.085, 0)),
(-144, ('[NPC][Abyssal][Gamma T4/T5]Triglavian', 0.463, 0.392, 0.447, 0.029)),
(-145, ('[NPC][Abyssal][Gamma T4/T5]Drifter', 0.428, 0.428, 0.428, 0.117)),
(-146, ('[NPC][Abyssal][Gamma T4/T5]Sleeper', 0.435, 0.435, 0.435, 0.215)),
(-147, ('[NPC][Abyssal][Gamma T4/T5]All', 0.493, 0.472, 0.5, 0.21))])
class TargetProfile:
# also determined import/export order - VERY IMPORTANT
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
DAMAGE_TYPES = ('em', 'thermal', 'kinetic', 'explosive')
_idealTarget = None
_builtins = None
def __init__(self, *args, **kwargs):
self.builtin = False
self.update(*args, **kwargs)
@reconstructor
def init(self):
self.builtin = False
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
@@ -44,7 +206,29 @@ class TargetProfile:
self._signatureRadius = signatureRadius
self._radius = radius
_idealTarget = None
@classmethod
def getBuiltinList(cls):
if cls._builtins is None:
cls.__generateBuiltins()
return list(cls._builtins.values())
@classmethod
def getBuiltinById(cls, id):
if cls._builtins is None:
cls.__generateBuiltins()
return cls._builtins.get(id)
@classmethod
def __generateBuiltins(cls):
cls._builtins = OrderedDict()
for id, data in BUILTINS.items():
rawName = data[0]
data = data[1:]
profile = TargetProfile(*data)
profile.ID = id
profile.rawName = rawName
profile.builtin = True
cls._builtins[id] = profile
@classmethod
def getIdeal(cls):
@@ -57,8 +241,9 @@ class TargetProfile:
maxVelocity=0,
signatureRadius=None,
radius=0)
cls._idealTarget.name = 'Ideal Target'
cls._idealTarget.ID = -1
cls._idealTarget.rawName = 'Ideal Target'
cls._idealTarget.ID = 0
cls._idealTarget.builtin = True
return cls._idealTarget
@property
@@ -100,7 +285,7 @@ class TargetProfile:
lookup = {}
current = eos.db.getTargetProfileList()
for pattern in current:
lookup[pattern.name] = pattern
lookup[pattern.rawName] = pattern
for line in lines:
try:
@@ -149,7 +334,7 @@ class TargetProfile:
eos.db.save(pattern)
else:
pattern = TargetProfile(**fields)
pattern.name = name.strip()
pattern.rawName = name.strip()
eos.db.save(pattern)
patterns.append(pattern)
@@ -166,7 +351,7 @@ class TargetProfile:
out += "# TargetProfile = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %],[Max velocity m/s],[Signature radius m],[Radius m]\n\n"
for dp in patterns:
out += cls.EXPORT_FORMAT % (
dp.name,
dp.rawName,
dp.emAmount * 100,
dp.thermalAmount * 100,
dp.kineticAmount * 100,
@@ -178,9 +363,39 @@ class TargetProfile:
return out.strip()
@property
def name(self):
return self.rawName
@property
def fullName(self):
categories, tail = self.__parseRawName()
return '{}{}'.format(''.join('[{}]'.format(c) for c in categories), tail)
@property
def shortName(self):
return self.__parseRawName()[1]
@property
def hierarchy(self):
return self.__parseRawName()[0]
def __parseRawName(self):
hierarchy = []
remainingName = self.rawName.strip() if self.rawName else ''
while True:
start, end = remainingName.find('['), remainingName.find(']')
if start == -1 or end == -1:
return hierarchy, remainingName
splitter = remainingName.find('|')
if splitter != -1 and splitter == start - 1:
return hierarchy, remainingName[1:]
hierarchy.append(remainingName[start + 1:end])
remainingName = remainingName[end + 1:].strip()
def __deepcopy__(self, memo):
p = TargetProfile(
self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount,
self._maxVelocity, self._signatureRadius, self._radius)
p.name = "%s copy" % self.name
p.rawName = "%s copy" % self.rawName
return p

View File

@@ -0,0 +1,39 @@
"""
Slightly modified version of function taken from here:
https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-525221546
"""
import pkgutil
def iterNamespace(name, path):
"""Pyinstaller-compatible namespace iteration.
Yields the name of all modules found at a given Fully-qualified path.
To have it running with pyinstaller, it requires to ensure a hook inject the
"hidden" modules from your plugins folder inside the executable:
- if your plugins are under the ``myappname/pluginfolder`` module
- create a file ``specs/hook-<myappname.pluginfolder>.py``
- content of this file should be:
.. code-block:: python
from PyInstaller.utils.hooks import collect_submodules
hiddenimports = collect_submodules('<myappname.pluginfolder>')
"""
prefix = name + "."
for p in pkgutil.iter_modules(path, prefix):
yield p[1]
# special handling when the package is bundled with PyInstaller 3.5
# See https://github.com/pyinstaller/pyinstaller/issues/1905#issuecomment-445787510
toc = set()
for importer in pkgutil.iter_importers(name.partition(".")[0]):
if hasattr(importer, 'toc'):
toc |= importer.toc
for name in toc:
if name.startswith(prefix):
yield name

View File

@@ -43,7 +43,7 @@ class BaseWrapper:
if self.isFit:
return '{} ({})'.format(self.item.name, self.item.ship.item.name)
elif self.isProfile:
return self.item.name
return self.item.fullName
return ''
@property
@@ -51,7 +51,7 @@ class BaseWrapper:
if self.isFit:
return '{} ({})'.format(self.item.name, self.item.ship.item.getShortName())
elif self.isProfile:
return self.item.name
return self.item.shortName
return ''
def getMaxVelocity(self, extraMultipliers=None, ignoreAfflictors=()):

View File

@@ -299,7 +299,10 @@ class ImplantDisplay(d.Display):
sourceContext1 = "implantItem" if fit.implantSource == ImplantLocation.FIT else "implantItemChar"
sourceContext2 = "implantItemMisc" if fit.implantSource == ImplantLocation.FIT else "implantItemMiscChar"
itemContext = None if mainImplant is None else Market.getInstance().getCategoryByItem(mainImplant.item).name
menu = ContextMenu.getMenu(self, mainImplant, selection, (sourceContext1, itemContext), (sourceContext2, itemContext))
menu = ContextMenu.getMenu(self, mainImplant, selection,
(sourceContext1, itemContext),
(sourceContext2, itemContext)
)
if menu:
self.PopupMenu(menu)

View File

@@ -36,7 +36,8 @@ from gui.builtinContextMenus import cargoAdd
from gui.builtinContextMenus import cargoAddAmmo
from gui.builtinContextMenus import itemProject
from gui.builtinContextMenus import ammoToDmgPattern
from gui.builtinContextMenus import implantSetAdd
from gui.builtinContextMenus import implantSetApply
from gui.builtinContextMenus import implantSetSave
# Price
from gui.builtinContextMenus import priceOptions
# Resistance panel

View File

@@ -3,7 +3,6 @@ from gui.contextMenu import ContextMenuUnconditional
from gui.utils.clipboard import toClipboard
from service.fit import Fit
from service.port.eft import exportDrones, exportFighters, exportCargo, exportImplants, exportBoosters
from service.settings import ContextMenuSettings
viewSpecMap = {
@@ -17,12 +16,12 @@ viewSpecMap = {
class AdditionsExportAll(ContextMenuUnconditional):
visibilitySetting = 'additionsCopyPaste'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
return False
if srcContext not in viewSpecMap:
return False
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())

View File

@@ -3,7 +3,6 @@ from gui.contextMenu import ContextMenuSelection
from gui.utils.clipboard import toClipboard
from service.fit import Fit
from service.port.eft import exportDrones, exportFighters, exportCargo, exportImplants, exportBoosters
from service.settings import ContextMenuSettings
viewSpecMap = {
@@ -17,12 +16,12 @@ viewSpecMap = {
class AdditionsExportAll(ContextMenuSelection):
visibilitySetting = 'additionsCopyPaste'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext, selection):
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
return False
if srcContext not in viewSpecMap:
return False
if not selection:

View File

@@ -4,7 +4,6 @@ from gui.contextMenu import ContextMenuUnconditional
from gui.utils.clipboard import fromClipboard
from service.fit import Fit
from service.port.eft import parseAdditions
from service.settings import ContextMenuSettings
viewSpecMap = {
@@ -18,12 +17,12 @@ viewSpecMap = {
class AdditionsImport(ContextMenuUnconditional):
visibilitySetting = 'additionsCopyPaste'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if not ContextMenuSettings.getInstance().get('additionsCopyPaste'):
return False
if srcContext not in viewSpecMap:
return False
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())

View File

@@ -5,19 +5,16 @@ import gui.globalEvents as GE
import gui.mainFrame
from gui.contextMenu import ContextMenuSingle
from service.fit import Fit
from service.settings import ContextMenuSettings
class AmmoToDmgPattern(ContextMenuSingle):
visibilitySetting = 'ammoPattern'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('ammoPattern'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False

View File

@@ -19,8 +19,8 @@ class AddToCargo(ContextMenuSingle):
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID)
# Make sure context menu registers in the correct view
if not fit or fit.isStructure:
if not fit or (fit.isStructure and mainItem.category.ID != 8):
return False
return True

View File

@@ -1,4 +1,5 @@
from collections import OrderedDict
from itertools import chain
# noinspection PyPackageRequirements
import wx
@@ -6,7 +7,8 @@ import wx
import gui.globalEvents as GE
import gui.mainFrame
from gui.contextMenu import ContextMenuUnconditional
from service.damagePattern import DamagePattern as import_DamagePattern
from gui.utils.sorter import smartSort
from service.damagePattern import DamagePattern as DmgPatternSvc
from service.fit import Fit
@@ -23,97 +25,90 @@ class ChangeDamagePattern(ContextMenuUnconditional):
return self.mainFrame.getActiveFit() is not None
def getText(self, callingWindow, itmContext):
sDP = import_DamagePattern.getInstance()
sDP = DmgPatternSvc.getInstance()
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
self.fit = sFit.getFit(fitID)
self.patterns = sDP.getDamagePatternList()
self.patterns.sort(key=lambda p: (p.name not in ["Uniform", "Selected Ammo"], p.name))
builtinPatterns = sDP.getBuiltinDamagePatternList()
userPatterns = sorted(sDP.getUserDamagePatternList(), key=lambda p: smartSort(p.fullName))
# Order here is important: patterns with duplicate names from the latter will overwrite
# patterns from the former
self.patterns = sorted(
chain(builtinPatterns, userPatterns),
key=lambda p: p.fullName not in ["Uniform", "Selected Ammo"])
self.patternIds = {}
self.subMenus = OrderedDict()
self.singles = []
# iterate and separate damage patterns based on "[Parent] Child"
self.patternEventMap = {}
self.items = (OrderedDict(), OrderedDict())
for pattern in self.patterns:
start, end = pattern.name.find('['), pattern.name.find(']')
if start is not -1 and end is not -1:
currBase = pattern.name[start + 1:end]
name = pattern.name[end + 1:].strip()
if not name:
self.singles.append(pattern)
continue
# set helper attr
setattr(pattern, "_name", name)
if currBase not in self.subMenus:
self.subMenus[currBase] = []
self.subMenus[currBase].append(pattern)
else:
self.singles.append(pattern)
container = self.items
for categoryName in pattern.hierarchy:
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
container[0][pattern.shortName] = pattern
# return list of names, with singles first followed by submenu names
self.m = [p.name for p in self.singles] + list(self.subMenus.keys())
return self.m
return list(self.items[0].keys()) + list(self.items[1].keys())
def addPattern(self, rootMenu, pattern):
def _addPattern(self, parentMenu, pattern, name):
id = ContextMenuUnconditional.nextID()
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
self.patternIds[id] = pattern
menuItem = wx.MenuItem(rootMenu, id, name, kind=wx.ITEM_CHECK)
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
# set pattern attr to menu item
menuItem.pattern = pattern
self.patternEventMap[id] = pattern
menuItem = wx.MenuItem(parentMenu, id, name, kind=wx.ITEM_CHECK)
parentMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
# determine active pattern
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID)
if fit:
dp = fit.damagePattern
checked = dp is pattern
else:
checked = False
checked = fit.damagePattern is pattern if fit else False
return menuItem, checked
def _addCategory(self, parentMenu, name):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, menuItem)
return menuItem
def isChecked(self, i):
try:
single = self.singles[i]
patternName = list(self.items[0].keys())[i]
except IndexError:
return super().isChecked(i)
if self.fit and single is self.fit.damagePattern:
pattern = self.items[0][patternName]
if self.fit and pattern is self.fit.damagePattern:
return True
return False
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
# Attempt to remove attribute which carries info if non-sub-items should
# be checked or not
self.checked = None
if self.m[i] not in self.subMenus:
# if we're trying to get submenu to something that shouldn't have one,
# redirect event of the item to handlePatternSwitch and put pattern in
# our patternIds mapping, then return None for no submenu
# Pattern as menu item
if i < len(self.items[0]):
id = pitem.GetId()
self.patternIds[id] = self.singles[i]
self.patternEventMap[id] = list(self.items[0].values())[i]
rootMenu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
return False
sub = wx.Menu()
# Items that have a parent
# Category as menu item - expands further
msw = "wxMSW" in wx.PlatformInfo
for pattern in self.subMenus[self.m[i]]:
mitem, checked = self.addPattern(rootMenu if msw else sub, pattern)
sub.Append(mitem)
mitem.Check(checked)
return sub
def makeMenu(container, parentMenu):
menu = wx.Menu()
for name, subcontainer in container[1].items():
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
subMenu = makeMenu(subcontainer, menu)
menuItem.SetSubMenu(subMenu)
menu.Append(menuItem)
for name, pattern in container[0].items():
menuItem, checked = self._addPattern(rootMenu if msw else parentMenu, pattern, name)
menu.Append(menuItem)
menuItem.Check(checked)
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch)
return menu
container = list(self.items[1].values())[i - len(self.items[0])]
subMenu = makeMenu(container, rootMenu)
return subMenu
def handlePatternSwitch(self, event):
pattern = self.patternIds.get(event.Id, False)
pattern = self.patternEventMap.get(event.Id, False)
if pattern is False:
event.Skip()
return

View File

@@ -5,7 +5,7 @@ from gui.contextMenu import ContextMenuUnconditional
from service.implantSet import ImplantSets as s_ImplantSets
class AddImplantSet(ContextMenuUnconditional):
class ImplantSetApply(ContextMenuUnconditional):
def display(self, callingWindow, srcContext):
@@ -14,10 +14,11 @@ class AddImplantSet(ContextMenuUnconditional):
if len(implantSets) == 0:
return False
return srcContext in ("implantItemMisc", "implantEditor")
def getText(self, callingWindow, itmContext):
return "Add Implant Set"
def getText(self, callingWindow, context):
return "Apply Implant Set"
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
m = wx.Menu()
@@ -49,4 +50,4 @@ class AddImplantSet(ContextMenuUnconditional):
self.callingWindow.addImplantSet(impSet)
AddImplantSet.register()
ImplantSetApply.register()

View File

@@ -0,0 +1,77 @@
import wx
import gui.mainFrame
from gui.contextMenu import ContextMenuUnconditional
from service.fit import Fit
class ImplantSetSave(ContextMenuUnconditional):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if srcContext not in ('implantItemMisc', 'implantItemMiscChar'):
return False
fit = Fit.getInstance().getFit(self.mainFrame.getActiveFit())
self.implants = fit.appliedImplants[:]
if not self.implants:
return False
return True
def getText(self, callingWindow, context):
return 'Save as New Implant Set'
def activate(self, callingWindow, fullContext, i):
with NameDialog(self.mainFrame, '') as dlg:
if dlg.ShowModal() == wx.ID_OK:
name = dlg.input.GetLineText(0).strip()
if name == '':
return
from gui.setEditor import ImplantSetEditor
ImplantSetEditor.openOne(parent=self.mainFrame, dataToAdd=(name, self.implants))
ImplantSetSave.register()
class NameDialog(wx.Dialog):
def __init__(self, parent, value):
super().__init__(parent, title='New Implant Set', style=wx.DEFAULT_DIALOG_STYLE)
self.SetMinSize((346, 156))
bSizer1 = wx.BoxSizer(wx.VERTICAL)
bSizer2 = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, 'Enter a name for your new Implant Set:')
bSizer2.Add(text, 0)
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
if value is None:
value = ''
else:
value = str(value)
self.input.SetValue(value)
self.input.SelectAll()
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.CenterOnParent()
self.Fit()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)

View File

@@ -1,19 +1,16 @@
import gui.fitCommands as cmd
import gui.mainFrame
from gui.contextMenu import ContextMenuSingle
from service.settings import ContextMenuSettings
class FillWithItem(ContextMenuSingle):
visibilitySetting = 'moduleFill'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('moduleFill'):
return False
if srcContext not in ('marketItemGroup', 'marketItemMisc'):
return False

View File

@@ -2,19 +2,16 @@ 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):
visibilitySetting = 'project'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('project'):
return False
if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None:
return False

View File

@@ -7,19 +7,16 @@ 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):
visibilitySetting = 'metaSwap'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem, selection):
if not self.settings.get('metaSwap'):
return False
if self.mainFrame.getActiveFit() is None or srcContext not in (
'fittingModule',
'droneItem',
@@ -59,8 +56,11 @@ class ChangeItemToVariation(ContextMenuCombined):
return x.metaLevel or 0
def get_metagroup(x):
# We want deadspace before officer mods
remap = {5: 6, 6: 5}
remap = {
# We want deadspace before officer mods
5: 6, 6: 5,
# For structures we want t1-t2-faction
54: 52, 52: 54}
metaGroup = sMkt.getMetaGroupByItem(x)
return remap.get(metaGroup.ID, metaGroup.ID) if metaGroup is not None else 0

View File

@@ -2,20 +2,16 @@ 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):
visibilitySetting = 'moduleFill'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('moduleFill'):
return False
if mainItem is None or getattr(mainItem, 'isEmpty', False):
return False

View File

@@ -9,20 +9,17 @@ 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):
visibilitySetting = 'spoolup'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
self.resetId = None
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('spoolup'):
return False
if srcContext not in ('fittingModule', 'projectedModule') or self.mainFrame.getActiveFit() is None:
return False

View File

@@ -8,19 +8,16 @@ 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(ContextMenuSingle):
visibilitySetting = 'changeAffectingSkills'
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.settings = ContextMenuSettings.getInstance()
def display(self, callingWindow, srcContext, mainItem):
if not self.settings.get('changeAffectingSkills'):
return False
if srcContext not in (
"fittingModule", "fittingCharge",
"fittingShip", "droneItem",

View File

@@ -7,6 +7,7 @@ import wx
import gui.mainFrame
from eos.saveddata.targetProfile import TargetProfile
from gui.contextMenu import ContextMenuUnconditional
from gui.utils.sorter import smartSort
from service.targetProfile import TargetProfile as svc_TargetProfile
@@ -18,74 +19,68 @@ class TargetProfileAdder(ContextMenuUnconditional):
def display(self, callingWindow, srcContext):
if srcContext != 'graphTgtList':
return False
sTR = svc_TargetProfile.getInstance()
self.callingWindow = callingWindow
self.profiles = sTR.getTargetProfileList()
self.profiles.sort(key=lambda p: (p.name in ['None'], p.name))
return len(self.profiles) > 0
# We always show "Ideal Profile" anyway
return True
def getText(self, callingWindow, itmContext):
return 'Add Target Profile'
def handleProfileAdd(self, event):
profile = self.profileIds.get(event.Id, False)
profile = self.profileEventMap.get(event.Id, False)
if profile is False:
event.Skip()
return
self.callingWindow.addProfile(profile)
def addProfile(self, rootMenu, profile):
def _addProfile(self, parentMenu, profile, name):
id = ContextMenuUnconditional.nextID()
name = getattr(profile, '_name', profile.name)
self.profileEventMap[id] = profile
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
return menuItem
self.profileIds[id] = profile
item = wx.MenuItem(rootMenu, id, name)
rootMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, item)
return item
def _addCategory(self, parentMenu, name):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleProfileAdd, menuItem)
return menuItem
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
self.profileIds = {}
self.subMenus = OrderedDict()
self.singles = []
self.callingWindow = callingWindow
sTR = svc_TargetProfile.getInstance()
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
profiles.sort(key=lambda p: smartSort(p.fullName))
sub = wx.Menu()
for profile in chain([TargetProfile.getIdeal()], self.profiles):
start, end = profile.name.find('['), profile.name.find(']')
if start is not -1 and end is not -1:
currBase = profile.name[start + 1:end]
# set helper attr
setattr(profile, '_name', profile.name[end + 1:].strip())
if currBase not in self.subMenus:
self.subMenus[currBase] = []
self.subMenus[currBase].append(profile)
else:
self.singles.append(profile)
self.profileEventMap = {}
items = (OrderedDict(), OrderedDict())
for profile in profiles:
container = items
for categoryName in profile.hierarchy:
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
container[0][profile.shortName] = profile
# Single items, no parent
msw = 'wxMSW' in wx.PlatformInfo
for profile in self.singles:
sub.Append(self.addProfile(rootMenu if msw else sub, profile))
# Category as menu item - expands further
msw = "wxMSW" in wx.PlatformInfo
# Items that have a parent
for menuName, profiles in list(self.subMenus.items()):
# Create parent item for root menu that is simply name of parent
item = wx.MenuItem(rootMenu, ContextMenuUnconditional.nextID(), menuName)
def makeMenu(container, parentMenu, first=False):
menu = wx.Menu()
if first:
idealProfile = TargetProfile.getIdeal()
mitem = self._addProfile(rootMenu if msw else parentMenu, idealProfile, idealProfile.fullName)
menu.Append(mitem)
for name, pattern in container[0].items():
menuItem = self._addProfile(rootMenu if msw else parentMenu, pattern, name)
menu.Append(menuItem)
for name, subcontainer in container[1].items():
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
subMenu = makeMenu(subcontainer, menu)
menuItem.SetSubMenu(subMenu)
menu.Append(menuItem)
menu.Bind(wx.EVT_MENU, self.handleProfileAdd)
return menu
# Create menu for child items
grandSub = wx.Menu()
# Apply child menu to parent item
item.SetSubMenu(grandSub)
# Append child items to child menu
for profile in profiles:
grandSub.Append(self.addProfile(rootMenu if msw else grandSub, profile))
sub.Append(item) # finally, append parent item to root menu
return sub
subMenu = makeMenu(items, rootMenu, first=True)
return subMenu
TargetProfileAdder.register()

View File

@@ -1,5 +1,4 @@
import gui.mainFrame
from eos.saveddata.targetProfile import TargetProfile
from graphs.wrapper import TargetWrapper
from gui.contextMenu import ContextMenuSingle
from gui.targetProfileEditor import TargetProfileEditor
@@ -17,7 +16,7 @@ class TargetProfileEditorMenu(ContextMenuSingle):
return False
if not mainItem.isProfile:
return False
if mainItem.item is TargetProfile.getIdeal():
if mainItem.item.builtin:
return False
return True

View File

@@ -1,4 +1,5 @@
from collections import OrderedDict
from itertools import chain
# noinspection PyPackageRequirements
import wx
@@ -6,6 +7,7 @@ import wx
import gui.globalEvents as GE
import gui.mainFrame
from gui.contextMenu import ContextMenuUnconditional
from gui.utils.sorter import smartSort
from service.fit import Fit
from service.targetProfile import TargetProfile as svc_TargetProfile
@@ -16,21 +18,19 @@ class TargetProfileSwitcher(ContextMenuUnconditional):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if self.mainFrame.getActiveFit() is None or srcContext != 'firepowerViewFull':
if srcContext != 'firepowerViewFull':
return False
sTR = svc_TargetProfile.getInstance()
self.profiles = sTR.getTargetProfileList()
self.profiles.sort(key=lambda p: (p.name in ['None'], p.name))
return len(self.profiles) > 0
if self.mainFrame.getActiveFit() is None:
return False
# We always show "No Profile" anyway
return True
def getText(self, callingWindow, itmContext):
# We take into consideration just target resists, so call menu item accordingly
return 'Target Resists'
def handleResistSwitch(self, event):
profile = self.profileIds.get(event.Id, False)
profile = self.profileEventMap.get(event.Id, False)
if profile is False:
event.Skip()
return
@@ -40,77 +40,62 @@ class TargetProfileSwitcher(ContextMenuUnconditional):
sFit.setTargetProfile(fitID, profile)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,)))
def addProfile(self, rootMenu, profile):
def _addProfile(self, parentMenu, profile, name):
id = ContextMenuUnconditional.nextID()
name = getattr(profile, '_name', profile.name) if profile is not None else 'No Profile'
self.profileIds[id] = profile
item = wx.MenuItem(rootMenu, id, name, kind=wx.ITEM_CHECK)
rootMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, item)
self.profileEventMap[id] = profile
menuItem = wx.MenuItem(parentMenu, id, name, kind=wx.ITEM_CHECK)
parentMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, menuItem)
# determine active profile
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
f = sFit.getFit(fitID)
tr = f.targetProfile
checked = sFit.getFit(fitID).targetProfile is profile
return menuItem, checked
checked = tr == profile
return item, checked
def _addCategory(self, parentMenu, name):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(parentMenu, id, name)
parentMenu.Bind(wx.EVT_MENU, self.handleResistSwitch, menuItem)
return menuItem
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
self.profileIds = {}
self.subMenus = OrderedDict()
self.singles = []
sTR = svc_TargetProfile.getInstance()
profiles = list(chain(sTR.getBuiltinTargetProfileList(), sTR.getUserTargetProfileList()))
profiles.sort(key=lambda p: smartSort(p.fullName))
sub = wx.Menu()
for profile in self.profiles:
start, end = profile.name.find('['), profile.name.find(']')
if start is not -1 and end is not -1:
currBase = profile.name[start + 1:end]
name = profile.name[end + 1:].strip()
if not name:
self.singles.append(profile)
continue
# set helper attr
setattr(profile, '_name', name)
if currBase not in self.subMenus:
self.subMenus[currBase] = []
self.subMenus[currBase].append(profile)
else:
self.singles.append(profile)
# Add reset
msw = 'wxMSW' in wx.PlatformInfo
mitem, checked = self.addProfile(rootMenu if msw else sub, None)
sub.Append(mitem)
mitem.Check(checked)
sub.AppendSeparator()
self.profileEventMap = {}
items = (OrderedDict(), OrderedDict())
for profile in profiles:
container = items
for categoryName in profile.hierarchy:
container = container[1].setdefault(categoryName, (OrderedDict(), OrderedDict()))
container[0][profile.shortName] = profile
# Single items, no parent
for profile in self.singles:
mitem, checked = self.addProfile(rootMenu if msw else sub, profile)
sub.Append(mitem)
mitem.Check(checked)
# Category as menu item - expands further
msw = "wxMSW" in wx.PlatformInfo
# Items that have a parent
for menuName, profiles in list(self.subMenus.items()):
# Create parent item for root menu that is simply name of parent
item = wx.MenuItem(rootMenu, ContextMenuUnconditional.nextID(), menuName)
# Create menu for child items
grandSub = wx.Menu()
# Apply child menu to parent item
item.SetSubMenu(grandSub)
# Append child items to child menu
for profile in profiles:
mitem, checked = self.addProfile(rootMenu if msw else grandSub, profile)
grandSub.Append(mitem)
def makeMenu(container, parentMenu, first=False):
menu = wx.Menu()
if first:
mitem, checked = self._addProfile(rootMenu if msw else parentMenu, None, 'No Profile')
menu.Append(mitem)
mitem.Check(checked)
sub.Append(item) # finally, append parent item to root menu
if len(container[0]) > 0 or len(container[1]) > 0:
menu.AppendSeparator()
for name, pattern in container[0].items():
menuItem, checked = self._addProfile(rootMenu if msw else parentMenu, pattern, name)
menu.Append(menuItem)
menuItem.Check(checked)
for name, subcontainer in container[1].items():
menuItem = self._addCategory(rootMenu if msw else parentMenu, name)
subMenu = makeMenu(subcontainer, menu)
menuItem.SetSubMenu(subMenu)
menu.Append(menuItem)
menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
return menu
return sub
subMenu = makeMenu(items, rootMenu, first=True)
return subMenu
TargetProfileSwitcher.register()

View File

@@ -10,12 +10,13 @@ _ValueChanged, EVT_VALUE_CHANGED = wx.lib.newevent.NewEvent()
class AttributeSliderChangeEvent:
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=True):
self.__obj = obj
self.__old = old_value
self.__new = new_value
self.__old_percent = old_percentage
self.__new_percent = new_percentage
self.__affect_modified_flag = affect_modified_flag
def GetObj(self):
return self.__obj
@@ -32,6 +33,10 @@ class AttributeSliderChangeEvent:
def GetPercentage(self):
return self.__new_percent
@property
def AffectsModifiedFlag(self):
return self.__affect_modified_flag
Object = property(GetObj)
OldValue = property(GetOldValue)
Value = property(GetValue)
@@ -40,9 +45,9 @@ class AttributeSliderChangeEvent:
class ValueChanged(_ValueChanged, AttributeSliderChangeEvent):
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage):
def __init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=True):
_ValueChanged.__init__(self)
AttributeSliderChangeEvent.__init__(self, obj, old_value, new_value, old_percentage, new_percentage)
AttributeSliderChangeEvent.__init__(self, obj, old_value, new_value, old_percentage, new_percentage, affect_modified_flag=affect_modified_flag)
class AttributeSlider(wx.Panel):
@@ -118,7 +123,7 @@ class AttributeSlider(wx.Panel):
self.SetValue(self.GetValue())
evt.Skip()
def SetValue(self, value, post_event=True):
def SetValue(self, value, post_event=True, affect_modified_flag=True):
self.ctrl.SetValue(value)
invert_factor = -1 if self.inverse else 1
if value >= self.base_value:
@@ -127,7 +132,7 @@ class AttributeSlider(wx.Panel):
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
self.slider.SetValue(slider_percentage)
if post_event:
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage))
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage, affect_modified_flag=affect_modified_flag))
def OnMouseWheel(self, evt):
if evt.GetWheelRotation() > 0 and evt.GetWheelAxis() == wx.MOUSE_WHEEL_VERTICAL:

View File

@@ -76,6 +76,7 @@ class ItemMutatorList(wx.ScrolledWindow):
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
self.mod = mod
self.timer = None
self.isModified = False
goodColor = wx.Colour(96, 191, 0)
badColor = wx.Colour(255, 64, 0)
@@ -171,6 +172,8 @@ class ItemMutatorList(wx.ScrolledWindow):
self.SetSizer(sizer)
def changeMutatedValue(self, evt):
if evt.AffectsModifiedFlag:
self.isModified = True
m = self.event_mapping[evt.Object]
value = evt.Value
value = m.attribute.unit.ComplicateValue(value)
@@ -188,29 +191,32 @@ class ItemMutatorList(wx.ScrolledWindow):
self.timer = wx.CallLater(1000, self.callLater)
def resetMutatedValues(self, evt):
self.isModified = True
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
value = sFit.changeMutatedValuePrelim(m, m.baseValue)
value = m.attribute.unit.SimplifyValue(value)
slider.SetValue(value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
def randomMutatedValues(self, evt):
self.isModified = True
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
value = random.uniform(m.minValue, m.maxValue)
value = sFit.changeMutatedValuePrelim(m, value)
value = m.attribute.unit.SimplifyValue(value)
slider.SetValue(value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
def revertChanges(self, evt):
self.isModified = False
sFit = Fit.getInstance()
for slider, m in self.event_mapping.items():
if m.attrID in self.initialMutations:
value = sFit.changeMutatedValuePrelim(m, self.initialMutations[m.attrID])
value = m.attribute.unit.SimplifyValue(value)
slider.SetValue(value)
slider.SetValue(value, affect_modified_flag=False)
evt.Skip()
def OnWindowClose(self):
@@ -218,15 +224,18 @@ class ItemMutatorList(wx.ScrolledWindow):
sFit = Fit.getInstance()
fit = sFit.getFit(self.carryingFitID)
if self.mod in fit.modules:
currentMutation = {}
for slider, m in self.event_mapping.items():
# Sliders may have more up-to-date info than mutator in case we changed
# value in slider and without confirming it, decided to close window
value = slider.GetValue()
value = m.attribute.unit.ComplicateValue(value)
if value != m.value:
value = sFit.changeMutatedValuePrelim(m, value)
currentMutation[m.attrID] = value
if self.isModified:
currentMutation = {}
for slider, m in self.event_mapping.items():
# Sliders may have more up-to-date info than mutator in case we changed
# value in slider and without confirming it, decided to close window
value = slider.GetValue()
value = m.attribute.unit.ComplicateValue(value)
if value != m.value:
value = sFit.changeMutatedValuePrelim(m, value)
currentMutation[m.attrID] = value
else:
currentMutation = self.initialMutations
mainFrame = gui.mainFrame.MainFrame.getInstance()
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand(
fitID=self.carryingFitID,

View File

@@ -21,9 +21,12 @@ class PFContextMenuPref(PreferenceView):
self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString))
mainSizer.Add(self.stTitle, 0, wx.EXPAND | wx.ALL, 5)
self.stSubTitle = wx.StaticText(panel, wx.ID_ANY,
"Disabling context menus can improve responsiveness.",
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle = wx.StaticText(
panel, wx.ID_ANY,
'Disabling context menus can improve responsiveness.\n'
'You can hold {} key + right-click to show all menu items regardless of these settings.'.format(
'Command' if 'wxMac' in wx.PlatformInfo else 'Control'),
wx.DefaultPosition, wx.DefaultSize, 0)
self.stSubTitle.Wrap(-1)
mainSizer.Add(self.stSubTitle, 0, wx.ALL, 5)

View File

@@ -1,7 +1,6 @@
import wx
import config
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
from eos.db.saveddata.queries import clearPrices, clearDamagePatterns, clearTargetProfiles
from gui.bitmap_loader import BitmapLoader
from gui.preferenceView import PreferenceView
@@ -75,10 +74,6 @@ class PFGeneralPref(PreferenceView):
btnSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer.AddStretchSpacer()
self.btnImportDefaults = wx.Button(panel, wx.ID_ANY, "Reimport Database Defaults", wx.DefaultPosition, wx.DefaultSize, 0)
btnSizer.Add(self.btnImportDefaults, 0, wx.ALL, 5)
self.btnImportDefaults.Bind(wx.EVT_BUTTON, self.loadDatabaseDefaults)
self.btnDeleteDamagePatterns = wx.Button(panel, wx.ID_ANY, "Delete All Damage Pattern Profiles", wx.DefaultPosition, wx.DefaultSize, 0)
btnSizer.Add(self.btnDeleteDamagePatterns, 0, wx.ALL, 5)
self.btnDeleteDamagePatterns.Bind(wx.EVT_BUTTON, self.DeleteDamagePatterns)
@@ -97,14 +92,6 @@ class PFGeneralPref(PreferenceView):
panel.SetSizer(mainSizer)
panel.Layout()
def loadDatabaseDefaults(self, event):
# Import values that must exist otherwise Pyfa breaks
DefaultDatabaseValues.importRequiredDefaults()
# Import default values for damage profiles
DefaultDatabaseValues.importDamageProfileDefaults()
# Import default values for target resist profiles
DefaultDatabaseValues.importTargetProfileDefaults()
def DeleteDamagePatterns(self, event):
question = "This is a destructive action that will delete all damage pattern profiles.\nAre you sure you want to do this?"
if wxHelpers.YesNoDialog(question, "Confirm"):

View File

@@ -21,7 +21,7 @@
import wx
from gui.statsView import StatsView
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundToPrec
class CapacitorViewFull(StatsView):
@@ -133,14 +133,20 @@ class CapacitorViewFull(StatsView):
label.SetLabel('{}{}'.format(formatAmount(value, prec, lowest, highest, forceSign=forceSign), unit))
label.SetToolTip(wx.ToolTip("%.1f" % value))
if labelName == 'label%sCapacitorDelta':
label_tooltip = 'Capacitor delta:\n+{} GJ/s\n-{} GJ/s'.format(
formatAmount(cap_recharge, 3, 0, 3),
formatAmount(cap_use, 3, 0, 3))
label.SetToolTip(wx.ToolTip(label_tooltip))
if labelName == 'label%sCapacitorDelta' and (cap_recharge or cap_use):
lines = []
lines.append('Capacitor delta:')
lines.append(' +{} GJ/s'.format(formatAmount(cap_recharge, 3, 0, 3)))
lines.append(' -{} GJ/s'.format(formatAmount(cap_use, 3, 0, 3)))
delta = round(cap_recharge - cap_use, 3)
if delta > 0 and 0 < round(neut_res, 4) < 1:
lines.append('')
lines.append('Effective excessive gain:')
lines.append(' +{} GJ/s'.format(formatAmount(delta / neut_res, 3, 0, 3)))
label.SetToolTip(wx.ToolTip('\n'.join(lines)))
if labelName == 'label%sCapacitorResist':
texts = ['Neutralizer resistance']
if cap_amount > 0 and neut_res < 1:
if cap_amount > 0 and 0 < round(neut_res, 4) < 1:
texts.append('Effective capacity: {} GJ'.format(formatAmount(cap_amount / neut_res, 3, 0, 9)))
label.SetToolTip(wx.ToolTip('\n'.join(texts)))

View File

@@ -151,52 +151,71 @@ class FirepowerViewFull(StatsView):
else:
self.stEff.Hide()
def dpsToolTip(preSpool, fullSpool, prec, lowest, highest):
if roundToPrec(preSpool, prec) == roundToPrec(fullSpool, prec):
def hasSpoolUp(preSpool, fullSpool):
if preSpool is None or fullSpool is None:
return False
return roundToPrec(preSpool.total, prec) != roundToPrec(fullSpool.total, prec)
def dpsToolTip(normal, preSpool, fullSpool, prec, lowest, highest):
if normal is None or preSpool is None or fullSpool is None:
return ""
else:
return "Spool up: {}-{}".format(
formatAmount(preSpool, prec, lowest, highest),
formatAmount(fullSpool, prec, lowest, highest))
hasSpool = hasSpoolUp(preSpool, fullSpool)
lines = []
if hasSpool:
lines.append("Spool up: {}-{}".format(
formatAmount(preSpool.total, prec, lowest, highest),
formatAmount(fullSpool.total, prec, lowest, highest)))
if getattr(normal, 'total', None):
if hasSpool:
lines.append("")
lines.append("Current: {}".format(formatAmount(normal.total, prec, lowest, highest)))
for dmgType in normal.names():
val = getattr(normal, dmgType, None)
if val:
lines.append("{}{}: {}%".format(
" " if hasSpool else "",
dmgType.capitalize(),
formatAmount(val / normal.total * 100, 3, 0, 0)))
return "\n".join(lines)
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
stats = (
(
"labelFullDpsWeapon",
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
lambda: fit.getWeaponDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
3, 0, 0, "{}{} DPS"),
(
"labelFullDpsDrone",
lambda: fit.getDroneDps().total,
lambda: fit.getDroneDps().total,
lambda: fit.getDroneDps().total,
lambda: fit.getDroneDps(),
lambda: fit.getDroneDps(),
lambda: fit.getDroneDps(),
3, 0, 0, "{}{} DPS"),
(
"labelFullVolleyTotal",
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
lambda: fit.getTotalVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
3, 0, 0, "{}{}"),
(
"labelFullDpsTotal",
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)).total,
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)).total,
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)).total,
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)),
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 0, True)),
lambda: fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, 1, True)),
3, 0, 0, "{}{}"))
counter = 0
for labelName, val, preSpoolVal, fullSpoolVal, prec, lowest, highest, valueFormat in stats:
label = getattr(self, labelName)
val = val() if fit is not None else 0
preSpoolVal = preSpoolVal() if fit is not None else 0
fullSpoolVal = fullSpoolVal() if fit is not None else 0
val = val() if fit is not None else None
preSpoolVal = preSpoolVal() if fit is not None else None
fullSpoolVal = fullSpoolVal() if fit is not None else None
if self._cachedValues[counter] != val:
tooltipText = dpsToolTip(preSpoolVal, fullSpoolVal, prec, lowest, highest)
tooltipText = dpsToolTip(val, preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(val, prec, lowest, highest),
"\u02e2" if tooltipText else ""))
formatAmount(0 if val is None else val.total, prec, lowest, highest),
"\u02e2" if hasSpoolUp(preSpoolVal, fullSpoolVal) else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
counter += 1

View File

@@ -111,7 +111,7 @@ class BaseName(ViewColumn):
elif isinstance(stuff, Implant):
return stuff.item.name
elif isinstance(stuff, TargetProfile):
return stuff.name
return stuff.shortName
else:
item = getattr(stuff, "item", stuff)

View File

@@ -339,23 +339,25 @@ class Miscellanea(ViewColumn):
formatAmount(radar, 3, 0, 3),
)
return text, tooltip
elif itemGroup in ("Remote Sensor Booster", "Sensor Booster", "Signal Amplifier"):
elif itemGroup in ("Remote Sensor Booster", "Sensor Booster", "Signal Amplifier", "Structure Signal Amplifier"):
textLines = []
tooltipLines = []
scanResBonus = stuff.getModifiedItemAttr("scanResolutionBonus")
if scanResBonus:
textLines.append("{}%".format(formatAmount(scanResBonus, 3, 0, 3)))
tooltipLines.append("{}% scan resolution".format(formatAmount(scanResBonus, 3, 0, 3)))
lockRangeBonus = stuff.getModifiedItemAttr("maxTargetRangeBonus")
if lockRangeBonus:
textLines.append("{}%".format(formatAmount(lockRangeBonus, 3, 0, 3)))
tooltipLines.append("{}% lock range".format(formatAmount(lockRangeBonus, 3, 0, 3)))
gravBonus = stuff.getModifiedItemAttr("scanGravimetricStrengthPercent")
if scanResBonus is None or lockRangeBonus is None or gravBonus is None:
if gravBonus:
textLines.append("{}%".format(formatAmount(gravBonus, 3, 0, 3)))
tooltipLines.append("{}% sensor strength".format(formatAmount(gravBonus, 3, 0, 3)))
if not textLines:
return "", None
text = "{0}% | {1}% | {2}%".format(
formatAmount(scanResBonus, 3, 0, 3),
formatAmount(lockRangeBonus, 3, 0, 3),
formatAmount(gravBonus, 3, 0, 3),
)
tooltip = "Applied bonuses:\n{0}% scan resolution | {1}% lock range | {2}% sensor strength".format(
formatAmount(scanResBonus, 3, 0, 3),
formatAmount(lockRangeBonus, 3, 0, 3),
formatAmount(gravBonus, 3, 0, 3),
)
text = " | ".join(textLines)
tooltip = "Applied bonuses:\n{}".format(" | ".join(tooltipLines))
return text, tooltip
elif itemGroup in ("Projected ECCM", "ECCM", "Sensor Backup Array"):
grav = stuff.getModifiedItemAttr("scanGravimetricStrengthPercent")
@@ -588,7 +590,7 @@ class Miscellanea(ViewColumn):
):
if "Armor" in itemGroup or "Shield" in itemGroup:
boosted_attribute = "HP"
reload_time = item.getAttribute("reloadTime", 0) / 1000
reload_time = stuff.getModifiedItemAttr("reloadTime", 0) / 1000
elif "Capacitor" in itemGroup:
boosted_attribute = "Cap"
reload_time = 10

View File

@@ -178,10 +178,17 @@ class EntityEditor(wx.Panel):
return True
def checkEntitiesExist(self):
if len(self.choices) == 0:
self.Parent.Hide()
if self.OnNew(None) is False:
return False
self.Parent.Show()
if len(self.choices) > 0:
return True
else:
return self.enterNewEntity()
def enterNewEntity(self):
self.Parent.Hide()
if self.OnNew(None) is False:
return False
self.Parent.Show()
return True

View File

@@ -428,6 +428,31 @@ class SkillTreeView(wx.Panel):
# This cuases issues with GTK, see #1866
# self.Layout()
# For level keyboard shortcuts
self.ChangeLevelEvent, CHANGE_LEVEL_EVENT = wx.lib.newevent.NewEvent()
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
self.Bind(CHANGE_LEVEL_EVENT, self.changeLevel)
def kbEvent(self, event):
keyLevelMap = {
# Regular number keys
48: 0, 49: 1, 50: 2, 51: 3, 52: 4, 53: 5,
# Numpad keys
wx.WXK_NUMPAD0: 0, wx.WXK_NUMPAD1: 1, wx.WXK_NUMPAD2: 2,
wx.WXK_NUMPAD3: 3, wx.WXK_NUMPAD4: 4, wx.WXK_NUMPAD5: 5}
keycode = event.GetKeyCode()
if keycode in keyLevelMap and event.GetModifiers() == wx.MOD_NONE:
level = keyLevelMap[keycode]
selection = self.skillTreeListCtrl.GetSelection()
if selection:
dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)
if dataType == 'skill':
event = self.ChangeLevelEvent()
event.SetId(self.idLevels[level])
wx.PostEvent(self, event)
return
event.Skip()
def importSkills(self, evt):
with wx.MessageDialog(
@@ -611,6 +636,8 @@ class SkillTreeView(wx.Panel):
sChar = Character.getInstance()
char = self.charEditor.entityEditor.getActiveEntity()
if char.name in ("All 0", "All 5"):
return
selection = self.skillTreeListCtrl.GetSelection()
dataType, skillID = self.skillTreeListCtrl.GetItemData(selection)

View File

@@ -23,6 +23,8 @@ from abc import ABCMeta, abstractmethod
import wx
from logbook import Logger
from service.settings import ContextMenuSettings
pyfalog = Logger(__name__)
@@ -32,6 +34,7 @@ class ContextMenu(metaclass=ABCMeta):
menus = []
_ids = [] # [wx.NewId() for x in xrange(200)] # init with decent amount
_idxid = -1
visibilitySetting = None
@classmethod
def register(cls):
@@ -72,6 +75,10 @@ class ContextMenu(metaclass=ABCMeta):
ContextMenu._idxid = -1
debug_start = len(ContextMenu._ids)
# Control being pressed forces all hidden menu items to be shown
visibilitySettingOverride = wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL
cmSettings = ContextMenuSettings.getInstance()
rootMenu = wx.Menu()
rootMenu.info = {}
rootMenu.selection = (selection,) if not hasattr(selection, "__iter__") else selection
@@ -87,7 +94,11 @@ class ContextMenu(metaclass=ABCMeta):
for menuHandler in cls.menus:
# loop through registered menus
m = menuHandler()
if m._baseDisplay(callingWindow, srcContext, mainItem, selection):
if m.visibilitySetting:
visible = visibilitySettingOverride or cmSettings.get(m.visibilitySetting)
else:
visible = True
if visible and m._baseDisplay(callingWindow, srcContext, mainItem, selection):
display_amount += 1
texts = m._baseGetText(callingWindow, itemContext, mainItem, selection)
@@ -182,12 +193,12 @@ class ContextMenu(metaclass=ABCMeta):
return ContextMenu._ids[ContextMenu._idxid]
def isChecked(self, i):
'''If menu item is toggleable, this should return bool value'''
"""If menu item is toggleable, this should return bool value"""
return None
@property
def enabled(self):
'''If menu item is enabled. Allows an item to display, but not be selected'''
"""If menu item is enabled. Allows an item to display, but not be selected"""
return True
@abstractmethod

View File

@@ -321,7 +321,10 @@ def activeStateLimit(itemIdentity):
item = Market.getInstance().getItem(itemIdentity)
if {
'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
'microJumpDrive', 'microJumpPortalDrive'
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively'
}.intersection(item.effects):
return FittingModuleState.ONLINE
return FittingModuleState.ACTIVE

View File

@@ -48,7 +48,7 @@ class DmgPatternNameValidator(BaseValidator):
try:
if len(text) == 0:
raise ValueError("You must supply a name for your Damage Profile!")
elif text in [x.name for x in entityEditor.choices]:
elif text in [x.rawName for x in entityEditor.choices]:
raise ValueError("Damage Profile name already in use, please choose another.")
return True
@@ -66,8 +66,8 @@ class DmgPatternEntityEditor(EntityEditor):
def getEntitiesFromContext(self):
sDP = DamagePattern.getInstance()
choices = sorted(sDP.getDamagePatternList(), key=lambda p: p.name)
return [c for c in choices if c.name != "Selected Ammo"]
choices = sorted(sDP.getUserDamagePatternList(), key=lambda p: p.rawName)
return [c for c in choices if c.rawName != "Selected Ammo"]
def DoNew(self, name):
sDP = DamagePattern.getInstance()
@@ -237,7 +237,7 @@ class DmgPatternEditor(AuxiliaryFrame):
if p is None:
return
if p.name == "Uniform" or p.name == "Selected Ammo":
if p.rawName == "Uniform" or p.rawName == "Selected Ammo":
self.restrict()
else:
self.unrestrict()

View File

@@ -47,7 +47,7 @@ class ImplantTextValidor(BaseValidator):
if len(text) == 0:
raise ValueError("You must supply a name for the Implant Set!")
elif text in [x.name for x in entityEditor.choices]:
raise ValueError("Imlplant Set name already in use, please choose another.")
raise ValueError("Implant Set name already in use, please choose another.")
return True
except ValueError as e:
@@ -106,7 +106,7 @@ class ImplantSetEditorView(BaseImplantEditorView):
sIS = ImplantSets.getInstance()
set_ = self.Parent.entityEditor.getActiveEntity()
sIS.addImplant(set_.ID, item.ID)
sIS.addImplants(set_.ID, item.ID)
def removeImplantFromContext(self, implant):
sIS = ImplantSets.getInstance()
@@ -117,7 +117,7 @@ class ImplantSetEditorView(BaseImplantEditorView):
class ImplantSetEditor(AuxiliaryFrame):
def __init__(self, parent):
def __init__(self, parent, dataToAdd=None):
super().__init__(
parent, id=wx.ID_ANY, title="Implant Set Editor", resizeable=True,
size=wx.Size(950, 500) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 420))
@@ -166,7 +166,13 @@ class ImplantSetEditor(AuxiliaryFrame):
self.SetSizer(mainSizer)
self.Layout()
if not self.entityEditor.checkEntitiesExist():
if dataToAdd:
name, implants = dataToAdd
newSet = self.entityEditor.DoNew(name)
ImplantSets.getInstance().addImplants(newSet.ID, *[i.item.ID for i in implants])
self.entityEditor.refreshEntityList(newSet)
wx.PostEvent(self.entityEditor.entityChoices, wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED))
elif not self.entityEditor.checkEntitiesExist():
self.Close()
return

View File

@@ -189,7 +189,7 @@ class ShipBrowser(wx.Panel):
RACE_ORDER = [
"amarr", "caldari", "gallente", "minmatar",
"sisters", "ore",
"sisters", "ore", "concord",
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
"jove", "upwell", "triglavian", None
]

View File

@@ -68,7 +68,7 @@ class TargetProfileNameValidator(BaseValidator):
try:
if len(text) == 0:
raise ValueError("You must supply a name for your Target Profile!")
elif text in [x.name for x in entityEditor.choices]:
elif text in [x.rawName for x in entityEditor.choices]:
raise ValueError("Target Profile name already in use, please choose another.")
return True
@@ -88,7 +88,7 @@ class TargetProfileEntityEditor(EntityEditor):
def getEntitiesFromContext(self):
sTR = TargetProfile.getInstance()
choices = sorted(sTR.getTargetProfileList(), key=lambda p: p.name)
choices = sorted(sTR.getUserTargetProfileList(), key=lambda p: p.rawName)
return choices
def DoNew(self, name):

13
gui/utils/sorter.py Normal file
View File

@@ -0,0 +1,13 @@
"""
Taken from https://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python
"""
import re
def _convert(text):
return int(text) if text.isdigit() else text
def smartSort(key):
return [_convert(c) for c in re.split('([0-9]+)', key)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/10864@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

BIN
imgs/icons/10864@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 726 B

After

Width:  |  Height:  |  Size: 939 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 B

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
imgs/icons/24175@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
imgs/icons/24175@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24228@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

BIN
imgs/icons/24228@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/icons/24229@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
imgs/icons/24229@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24230@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

BIN
imgs/icons/24230@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24237@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

BIN
imgs/icons/24237@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Some files were not shown because too many files have changed in this diff Show More