Merge tag 'v2.4.0' into commandRefactor

# Conflicts:
#	gui/builtinViews/fittingView.py
#	service/fit.py
This commit is contained in:
blitzmann
2018-08-23 21:15:28 -04:00
44 changed files with 845 additions and 140 deletions

View File

@@ -24,10 +24,10 @@ saveInRoot = False
# Version data
version = "2.3.1"
version = "2.4.0"
tag = "Stable"
expansionName = "YC120.7"
expansionVersion = "1.2"
expansionName = "YC120.8"
expansionVersion = "1.0"
evemonMinVersion = "4081"
minItemSearchLength = 3

View File

@@ -1,7 +1,7 @@
# boosterShieldCapacityPenalty
#
# Used by:
# Implants from group: Booster (12 of 66)
# Implants from group: Booster (12 of 65)
type = "boosterSideEffect"
# User-friendly name for the side effect

View File

@@ -3,9 +3,9 @@
# Used by:
# Ships from group: Black Ops (5 of 5)
# Ships from group: Blockade Runner (4 of 4)
# Ships from group: Covert Ops (7 of 7)
# Ships from group: Covert Ops (8 of 8)
# Ships from group: Expedition Frigate (2 of 2)
# Ships from group: Force Recon Ship (8 of 8)
# Ships from group: Force Recon Ship (9 of 9)
# Ships from group: Stealth Bomber (5 of 5)
# Ships named like: Stratios (2 of 2)
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)

View File

@@ -1,7 +1,7 @@
# covertOpsCloakCpuPercentBonus1
#
# Used by:
# Ships from group: Covert Ops (5 of 7)
# Ships from group: Covert Ops (6 of 8)
type = "passive"
runTime = "early"

View File

@@ -0,0 +1,9 @@
# covertOpsWarpResistance
#
# Used by:
# Ships from group: Covert Ops (5 of 8)
type = "passive"
def handler(fit, src, context):
fit.ship.increaseItemAttr("warpFactor", src.getModifiedItemAttr("eliteBonusCovertOps1"), skill="Covert Ops")

View File

@@ -1,7 +1,7 @@
# cynosuralDurationBonus
#
# Used by:
# Ships from group: Force Recon Ship (7 of 8)
# Ships from group: Force Recon Ship (8 of 9)
type = "passive"

View File

@@ -1,7 +1,7 @@
# cynosuralTheoryConsumptionBonus
#
# Used by:
# Ships from group: Force Recon Ship (7 of 8)
# Ships from group: Force Recon Ship (8 of 9)
# Skill: Cynosural Field Theory
type = "passive"

View File

@@ -1,7 +1,7 @@
# eliteBonusCoverOpsScanProbeStrength2
#
# Used by:
# Ships from group: Covert Ops (7 of 7)
# Ships from group: Covert Ops (8 of 8)
type = "passive"

View File

@@ -0,0 +1,10 @@
# eliteBonusMaxDmgMultiBonusAdd
#
# Used by:
# Ship: Hydra
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"), "damageMultiplierBonusMax",
src.getModifiedItemAttr("eliteBonusCovertOps3"), skill="Covert Ops")

View File

@@ -0,0 +1,10 @@
# eliteBonusReconMaxDmgMultiMaxHPT
#
# Used by:
# Ship: Tiamat
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Medium Precursor Weapon"), "damageMultiplierBonusMax",
src.getModifiedItemAttr("eliteBonusReconShip3"), skill="Recon Ships")

View File

@@ -0,0 +1,10 @@
# eliteBonusReconScanProbeStrength2
#
# Used by:
# Ship: Tiamat
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Astrometrics"), "baseSensorStrength",
src.getModifiedItemAttr("eliteBonusReconShip2"), skill="Recon Ships")

View File

@@ -1,7 +1,7 @@
# minigameVirusStrengthBonus
#
# Used by:
# Ships from group: Covert Ops (7 of 7)
# Ships from group: Covert Ops (7 of 8)
# Ships named like: Stratios (2 of 2)
# Subsystems named like: Defensive Covert Reconfiguration (4 of 4)
# Ship: Astero

View File

@@ -1,7 +1,7 @@
# reconShipCloakCpuBonus1
#
# Used by:
# Ships from group: Force Recon Ship (6 of 8)
# Ships from group: Force Recon Ship (7 of 9)
type = "passive"
runTime = "early"

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -1,6 +1,7 @@
# shipbonusPCTDamagePC1
#
# Used by:
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
type = "passive"

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
type = "passive"

View File

@@ -1,6 +1,7 @@
# shipbonusPCTTrackingPC2
#
# Used by:
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Hydra
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak
type = "passive"

View File

@@ -1,7 +1,7 @@
# shipBonusSurveyProbeExplosionDelaySkillSurveyCovertOps3
#
# Used by:
# Ships from group: Covert Ops (5 of 7)
# Ships from group: Covert Ops (5 of 8)
type = "passive"

BIN
eve.db

Binary file not shown.

View File

@@ -29,19 +29,21 @@ class CopySelectDialog(wx.Dialog):
copyFormatDna = 3
copyFormatEsi = 4
copyFormatMultiBuy = 5
copyFormatEfs = 6
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1),
style=wx.DEFAULT_DIALOG_STYLE)
mainSizer = wx.BoxSizer(wx.VERTICAL)
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy"]
copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy", "EFS"]
copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
CopySelectDialog.copyFormatEftImps: "EFT text format",
CopySelectDialog.copyFormatXml: "EVE native XML format",
CopySelectDialog.copyFormatDna: "A one-line text format",
CopySelectDialog.copyFormatEsi: "A JSON format used for EVE CREST",
CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format"}
CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format",
CopySelectDialog.copyFormatEfs: "JSON data format used by EFS"}
selector = wx.RadioBox(self, wx.ID_ANY, label="Copy to the clipboard using:", choices=copyFormats,
style=wx.RA_SPECIFY_ROWS)
selector.Bind(wx.EVT_RADIOBOX, self.Selected)

View File

@@ -77,6 +77,7 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict
from eos.db.saveddata.loadDefaultDatabaseValues import DefaultDatabaseValues
from eos.db.saveddata.queries import getFit as db_getFit
from service.port import Port, IPortUser
from service.efsPort import EfsPort
from service.settings import HTMLExportSettings
from time import gmtime, strftime
@@ -734,6 +735,10 @@ class MainFrame(wx.Frame):
fit = db_getFit(self.getActiveFit())
toClipboard(Port.exportMultiBuy(fit))
def clipboardEfs(self):
fit = db_getFit(self.getActiveFit())
toClipboard(EfsPort.exportEfs(fit, 0))
def importFromClipboard(self, event):
clipboard = fromClipboard()
try:
@@ -749,7 +754,8 @@ class MainFrame(wx.Frame):
CopySelectDialog.copyFormatXml: self.clipboardXml,
CopySelectDialog.copyFormatDna: self.clipboardDna,
CopySelectDialog.copyFormatEsi: self.clipboardEsi,
CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy}
CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy,
CopySelectDialog.copyFormatEfs: self.clipboardEfs}
dlg = CopySelectDialog(self)
dlg.ShowModal()
selected = dlg.GetSelected()

BIN
imgs/icons/10160.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

BIN
imgs/icons/10851.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

BIN
imgs/icons/21785.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

BIN
imgs/icons/22029.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
imgs/icons/22030.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

BIN
imgs/icons/22031.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

BIN
imgs/icons/22034.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

BIN
imgs/icons/22036.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 B

BIN
imgs/icons/22041.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

BIN
imgs/renders/21344.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/renders/21362.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/renders/21405.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
imgs/renders/21406.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -141,6 +141,7 @@ def get_icon_file(res_path, size):
icon for it. Return as PIL image object down-
scaled for use in pyfa.
"""
res_path = res_path.replace('//', '/') #1703
if res_path not in res_index:
return None
res_icon = res_index[res_path]
@@ -192,7 +193,10 @@ if toadd:
print(('Adding {} icons...'.format(len(toadd))))
missing = set()
for fname in sorted(toadd):
icon = icon_json[str(fname)]
icon = icon_json.get(str(fname), None)
if icon is None:
print("Can't find iconID {}".format(fname))
continue
key = icon['iconFile'].lower()
icon = get_icon_file(key, ICON_SIZE)
if icon is None:

View File

@@ -25,7 +25,7 @@ import re
# Add eos root path to sys.path so we can import ourselves
path = os.path.dirname(__file__)
sys.path.insert(0, os.path.realpath(os.path.join(path, "..")))
sys.path.insert(0, os.path.realpath(os.path.join(path, '..')))
import json
import argparse
@@ -54,65 +54,65 @@ def main(db, json_path):
# Config dict
tables = {
"clonegrades": eos.gamedata.AlphaCloneSkill,
"dgmattribs": eos.gamedata.AttributeInfo,
"dgmeffects": eos.gamedata.Effect,
"dgmtypeattribs": eos.gamedata.Attribute,
"dgmtypeeffects": eos.gamedata.ItemEffect,
"dgmunits": eos.gamedata.Unit,
"evecategories": eos.gamedata.Category,
"evegroups": eos.gamedata.Group,
"invmetagroups": eos.gamedata.MetaGroup,
"invmetatypes": eos.gamedata.MetaType,
"evetypes": eos.gamedata.Item,
"phbtraits": eos.gamedata.Traits,
"phbmetadata": eos.gamedata.MetaData,
"mapbulk_marketGroups": eos.gamedata.MarketGroup,
'clonegrades': eos.gamedata.AlphaCloneSkill,
'dgmattribs': eos.gamedata.AttributeInfo,
'dgmeffects': eos.gamedata.Effect,
'dgmtypeattribs': eos.gamedata.Attribute,
'dgmtypeeffects': eos.gamedata.ItemEffect,
'dgmunits': eos.gamedata.Unit,
'evecategories': eos.gamedata.Category,
'evegroups': eos.gamedata.Group,
'invmetagroups': eos.gamedata.MetaGroup,
'invmetatypes': eos.gamedata.MetaType,
'evetypes': eos.gamedata.Item,
'phbtraits': eos.gamedata.Traits,
'phbmetadata': eos.gamedata.MetaData,
'mapbulk_marketGroups': eos.gamedata.MarketGroup,
}
fieldMapping = {
"dgmattribs": {
"displayName_en-us": "displayName"
'dgmattribs': {
'displayName_en-us': 'displayName'
},
"dgmeffects": {
"displayName_en-us": "displayName",
"description_en-us": "description"
'dgmeffects': {
'displayName_en-us': 'displayName',
'description_en-us': 'description'
},
"dgmunits": {
"displayName_en-us": "displayName"
'dgmunits': {
'displayName_en-us': 'displayName'
},
#icons???
"evecategories": {
"categoryName_en-us": "categoryName"
'evecategories': {
'categoryName_en-us': 'categoryName'
},
"evegroups": {
"groupName_en-us": "groupName"
'evegroups': {
'groupName_en-us': 'groupName'
},
"invmetagroups": {
"metaGroupName_en-us": "metaGroupName"
'invmetagroups': {
'metaGroupName_en-us': 'metaGroupName'
},
"evetypes": {
"typeName_en-us": "typeName",
"description_en-us": "description"
'evetypes': {
'typeName_en-us': 'typeName',
'description_en-us': 'description'
},
#phbtraits???
"mapbulk_marketGroups": {
"marketGroupName_en-us": "marketGroupName",
"description_en-us": "description"
'mapbulk_marketGroups': {
'marketGroupName_en-us': 'marketGroupName',
'description_en-us': 'description'
}
}
rowsInValues = (
"evetypes",
"evegroups",
"evecategories"
'evetypes',
'evegroups',
'evecategories'
)
def convertIcons(data):
new = []
for k, v in list(data.items()):
v["iconID"] = k
v['iconID'] = k
new.append(v)
return new
@@ -126,23 +126,23 @@ def main(db, json_path):
check = {}
for ID in data:
for skill in data[ID]["skills"]:
for skill in data[ID]['skills']:
newData.append({
"alphaCloneID": int(ID),
"alphaCloneName": "Alpha Clone",
"typeID": skill["typeID"],
"level": skill["level"]})
'alphaCloneID': int(ID),
'alphaCloneName': 'Alpha Clone',
'typeID': skill['typeID'],
'level': skill['level']})
if ID not in check:
check[ID] = {}
check[ID][int(skill["typeID"])] = int(skill["level"])
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")
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")
raise Exception('Alpha Clone processing failed')
return newData
@@ -150,28 +150,28 @@ def main(db, json_path):
def convertSection(sectionData):
sectionLines = []
headerText = "<b>{}</b>".format(sectionData["header"])
headerText = '<b>{}</b>'.format(sectionData['header'])
sectionLines.append(headerText)
for bonusData in sectionData["bonuses"]:
prefix = "{} ".format(bonusData["number"]) if "number" in bonusData else ""
bonusText = "{}{}".format(prefix, bonusData["text"].replace("\u00B7", "\u2022 "))
for bonusData in sectionData['bonuses']:
prefix = '{} '.format(bonusData['number']) if 'number' in bonusData else ''
bonusText = '{}{}'.format(prefix, bonusData['text'].replace('\u00B7', '\u2022 '))
sectionLines.append(bonusText)
sectionLine = "<br />\n".join(sectionLines)
sectionLine = '<br />\n'.join(sectionLines)
return sectionLine
newData = []
for row in data:
typeLines = []
typeId = row["typeID"]
traitData = row["traits"]
for skillData in sorted(traitData.get("skills", ()), key=lambda i: i["header"]):
typeId = row['typeID']
traitData = row['traits_en-us']
for skillData in sorted(traitData.get('skills', ()), key=lambda i: i['header']):
typeLines.append(convertSection(skillData))
if "role" in traitData:
typeLines.append(convertSection(traitData["role"]))
if "misc" in traitData:
typeLines.append(convertSection(traitData["misc"]))
traitLine = "<br />\n<br />\n".join(typeLines)
newRow = {"typeID": typeId, "traitText": traitLine}
if 'role' in traitData:
typeLines.append(convertSection(traitData['role']))
if 'misc' in traitData:
typeLines.append(convertSection(traitData['misc']))
traitLine = '<br />\n<br />\n'.join(typeLines)
newRow = {'typeID': typeId, 'traitText': traitLine}
newData.append(newRow)
return newData
@@ -179,15 +179,15 @@ def main(db, json_path):
# Dump all data to memory so we can easely cross check ignored rows
for jsonName, cls in tables.items():
with open(os.path.join(jsonPath, "{}.json".format(jsonName)), encoding="utf-8") as f:
with open(os.path.join(jsonPath, '{}.json'.format(jsonName)), encoding='utf-8') as f:
tableData = json.load(f)
if jsonName in rowsInValues:
tableData = list(tableData.values())
if jsonName == "icons":
if jsonName == 'icons':
tableData = convertIcons(tableData)
if jsonName == "phbtraits":
if jsonName == 'phbtraits':
tableData = convertTraits(tableData)
if jsonName == "clonegrades":
if jsonName == 'clonegrades':
tableData = convertClones(tableData)
data[jsonName] = tableData
@@ -195,23 +195,23 @@ def main(db, json_path):
# 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"]
for row in data['evetypes']:
if (row['published']
or row['groupID'] == 1306 # group Ship Modifiers, for items like tactical t3 ship modes
or row['typeName'].startswith('Civilian') # Civilian weapons
or row['typeName_en-us'].startswith('Civilian') # Civilian weapons
or row['typeID'] in (41549, 41548, 41551, 41550) # Micro Bombs (Fighters)
or row['groupID'] in (
1882,
1975,
1971,
1983 # the "container" for the abysmal environments
) # Abysmal weather (environment)
1983 # the "container" for the abyssal environments
) # Abyssal weather (environment)
):
eveTypes.add(row["typeID"])
eveTypes.add(row['typeID'])
# ignore checker
def isIgnored(file, row):
if file in ("evetypes", "dgmtypeeffects", "dgmtypeattribs", "invmetatypes") and row['typeID'] not in eveTypes:
if file in ('evetypes', 'dgmtypeeffects', 'dgmtypeattribs', 'invmetatypes') and row['typeID'] not in eveTypes:
return True
return False
@@ -220,31 +220,31 @@ def main(db, json_path):
fieldMap = fieldMapping.get(jsonName, {})
tmp = []
print("processing {}".format(jsonName))
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 row["typeName"].startswith('Civilian'): # Apparently people really want Civilian modules available
row["published"] = True
if jsonName == 'evetypes' and row['typeName_en-us'].startswith('Civilian'): # Apparently people really want Civilian modules available
row['published'] = True
instance = tables[jsonName]()
# 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", "")
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", "")
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):
if jsonName is 'clonegrades':
if (row['alphaCloneID'] not in tmp):
cloneParent = eos.gamedata.AlphaClone()
setattr(cloneParent, "alphaCloneID", row["alphaCloneID"])
setattr(cloneParent, "alphaCloneName", row["alphaCloneName"])
setattr(cloneParent, 'alphaCloneID', row['alphaCloneID'])
setattr(cloneParent, 'alphaCloneName', row['alphaCloneName'])
eos.db.gamedata_session.add(cloneParent)
tmp.append(row['alphaCloneID'])
@@ -256,7 +256,7 @@ def main(db, json_path):
eos.db.gamedata_session.add(instance)
# quick and dirty hack to get this data in
with open(os.path.join(jsonPath, "dynamicAttributes.json"), encoding="utf-8") as f:
with open(os.path.join(jsonPath, 'dynamicAttributes.json'), encoding='utf-8') as f:
bulkdata = json.load(f)
for mutaID, data in bulkdata.items():
muta = eos.gamedata.DynamicItem()
@@ -283,25 +283,25 @@ def main(db, json_path):
# CCP still has 5 subsystems assigned to T3Cs, even though only 4 are available / usable. They probably have some
# old legacy requirement or assumption that makes it difficult for them to change this value in the data. But for
# pyfa, we can do it here as a post-processing step
eos.db.gamedata_engine.execute("UPDATE dgmtypeattribs SET value = 4.0 WHERE attributeID = ?", (1367,))
eos.db.gamedata_engine.execute('UPDATE dgmtypeattribs SET value = 4.0 WHERE attributeID = ?', (1367,))
eos.db.gamedata_engine.execute("UPDATE invtypes SET published = 0 WHERE typeName LIKE '%abyssal%'")
eos.db.gamedata_engine.execute('UPDATE invtypes SET published = 0 WHERE typeName LIKE \'%abyssal%\'')
print()
for x in CATEGORIES_TO_REMOVE:
cat = eos.db.gamedata_session.query(eos.gamedata.Category).filter(eos.gamedata.Category.ID == x).first()
print ("Removing Category: {}".format(cat.name))
print ('Removing Category: {}'.format(cat.name))
eos.db.gamedata_session.delete(cat)
eos.db.gamedata_session.commit()
eos.db.gamedata_engine.execute("VACUUM")
eos.db.gamedata_engine.execute('VACUUM')
print("done")
print('done')
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="This scripts dumps effects from an sqlite cache dump to mongo")
parser.add_argument("-d", "--db", required=True, type=str, help="The sqlalchemy connectionstring, example: sqlite:///c:/tq.db")
parser.add_argument("-j", "--json", required=True, type=str, help="The path to the json dump")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='This scripts dumps effects from an sqlite cache dump to mongo')
parser.add_argument('-d', '--db', required=True, type=str, help='The sqlalchemy connectionstring, example: sqlite:///c:/tq.db')
parser.add_argument('-j', '--json', required=True, type=str, help='The path to the json dump')
args = parser.parse_args()
main(args.db, args.json)

640
service/efsPort.py Executable file
View File

@@ -0,0 +1,640 @@
import inspect
import os
import platform
import re
import sys
import traceback
import json
import eos.db
from math import log
from config import version as pyfaVersion
from service.fit import Fit
from service.market import Market
from eos.enum import Enum
from eos.saveddata.module import Hardpoint, Slot, Module, State
from eos.saveddata.drone import Drone
from eos.effectHandlerHelpers import HandledList
from eos.db import gamedata_session, getItemsByCategory, getCategory, getAttributeInfo, getGroup
from eos.gamedata import Category, Group, Item, Traits, Attribute, Effect, ItemEffect
from logbook import Logger
pyfalog = Logger(__name__)
class RigSize(Enum):
# Matches to item attribute "rigSize" on ship and rig items
SMALL = 1
MEDIUM = 2
LARGE = 3
CAPITAL = 4
class EfsPort():
wepTestSet = {}
version = 0.01
@staticmethod
def attrDirectMap(values, target, source):
for val in values:
target[val] = source.getModifiedItemAttr(val)
@staticmethod
def getT2MwdSpeed(fit, sFit):
fitID = fit.ID
propID = None
shipHasMedSlots = fit.ship.getModifiedItemAttr("medSlots") > 0
shipPower = fit.ship.getModifiedItemAttr("powerOutput")
# Monitors have a 99% reduction to prop mod power requirements
if fit.ship.name == "Monitor":
shipPower *= 100
rigSize = fit.ship.getModifiedItemAttr("rigSize")
if not shipHasMedSlots:
return None
filterVal = Item.groupID == getGroup("Propulsion Module").ID
propMods = gamedata_session.query(Item).options().filter(filterVal).all()
mapPropData = lambda propName: \
next(map(lambda propMod: {"id": propMod.typeID, "powerReq": propMod.attributes["power"].value},
(filter(lambda mod: mod.name == propName, propMods))))
mwd5mn = mapPropData("5MN Microwarpdrive II")
mwd50mn = mapPropData("50MN Microwarpdrive II")
mwd500mn = mapPropData("500MN Microwarpdrive II")
mwd50000mn = mapPropData("50000MN Microwarpdrive II")
if rigSize == RigSize.SMALL or rigSize is None:
propID = mwd5mn["id"] if shipPower > mwd5mn["powerReq"] else None
elif rigSize == RigSize.MEDIUM:
propID = mwd50mn["id"] if shipPower > mwd50mn["powerReq"] else mwd5mn["id"]
elif rigSize == RigSize.LARGE:
propID = mwd500mn["id"] if shipPower > mwd500mn["powerReq"] else mwd50mn["id"]
elif rigSize == RigSize.CAPITAL:
propID = mwd50000mn["id"] if shipPower > mwd50000mn["powerReq"] else mwd500mn["id"]
if propID is None:
return None
sFit.appendModule(fitID, propID)
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
mwdPropSpeed = fit.maxSpeed
mwdPosition = list(filter(lambda mod: mod.item and mod.item.ID == propID, fit.modules))[0].position
sFit.removeModule(fitID, mwdPosition)
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
return mwdPropSpeed
@staticmethod
def getPropData(fit, sFit):
fitID = fit.ID
propMods = filter(lambda mod: mod.item and mod.item.group.name == "Propulsion Module", fit.modules)
activePropWBloomFilter = lambda mod: mod.state > 0 and "signatureRadiusBonus" in mod.item.attributes
propWithBloom = next(filter(activePropWBloomFilter, propMods), None)
if propWithBloom is not None:
oldPropState = propWithBloom.state
propWithBloom.state = State.ONLINE
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
sp = fit.maxSpeed
sig = fit.ship.getModifiedItemAttr("signatureRadius")
propWithBloom.state = oldPropState
sFit.recalc(fit)
fit = eos.db.getFit(fitID)
return {"usingMWD": True, "unpropedSpeed": sp, "unpropedSig": sig}
return {
"usingMWD": False,
"unpropedSpeed": fit.maxSpeed,
"unpropedSig": fit.ship.getModifiedItemAttr("signatureRadius")
}
@staticmethod
def getOutgoingProjectionData(fit):
# This is a subset of module groups capable of projection and a superset of those currently used by efs
modGroupNames = [
"Remote Shield Booster", "Warp Scrambler", "Stasis Web", "Remote Capacitor Transmitter",
"Energy Nosferatu", "Energy Neutralizer", "Burst Jammer", "ECM", "Sensor Dampener",
"Weapon Disruptor", "Remote Armor Repairer", "Target Painter", "Remote Hull Repairer",
"Burst Projectors", "Warp Disrupt Field Generator", "Armor Resistance Shift Hardener",
"Target Breaker", "Micro Jump Drive", "Ship Modifiers", "Stasis Grappler",
"Ancillary Remote Shield Booster", "Ancillary Remote Armor Repairer",
"Titan Phenomena Generator", "Non-Repeating Hardeners"
]
projectedMods = list(filter(lambda mod: mod.item and mod.item.group.name in modGroupNames, fit.modules))
projections = []
for mod in projectedMods:
maxRangeDefault = 0
falloffDefault = 0
stats = {}
if mod.item.group.name in ["Stasis Web", "Stasis Grappler"]:
stats["type"] = "Stasis Web"
stats["optimal"] = mod.getModifiedItemAttr("maxRange")
EfsPort.attrDirectMap(["duration", "speedFactor"], stats, mod)
elif mod.item.group.name == "Weapon Disruptor":
stats["type"] = "Weapon Disruptor"
stats["optimal"] = mod.getModifiedItemAttr("maxRange")
stats["falloff"] = mod.getModifiedItemAttr("falloffEffectiveness")
EfsPort.attrDirectMap([
"trackingSpeedBonus", "maxRangeBonus", "falloffBonus", "aoeCloudSizeBonus",
"aoeVelocityBonus", "missileVelocityBonus", "explosionDelayBonus"
], stats, mod)
elif mod.item.group.name == "Energy Nosferatu":
stats["type"] = "Energy Nosferatu"
EfsPort.attrDirectMap(["powerTransferAmount", "energyNeutralizerSignatureResolution"], stats, mod)
elif mod.item.group.name == "Energy Neutralizer":
stats["type"] = "Energy Neutralizer"
EfsPort.attrDirectMap([
"energyNeutralizerSignatureResolution", "entityCapacitorLevelModifierSmall",
"entityCapacitorLevelModifierMedium", "entityCapacitorLevelModifierLarge",
"energyNeutralizerAmount"
], stats, mod)
elif mod.item.group.name in ["Remote Shield Booster", "Ancillary Remote Shield Booster"]:
stats["type"] = "Remote Shield Booster"
EfsPort.attrDirectMap(["shieldBonus"], stats, mod)
elif mod.item.group.name in ["Remote Armor Repairer", "Ancillary Remote Armor Repairer"]:
stats["type"] = "Remote Armor Repairer"
EfsPort.attrDirectMap(["armorDamageAmount"], stats, mod)
elif mod.item.group.name == "Warp Scrambler":
stats["type"] = "Warp Scrambler"
EfsPort.attrDirectMap(["activationBlockedStrenght", "warpScrambleStrength"], stats, mod)
elif mod.item.group.name == "Target Painter":
stats["type"] = "Target Painter"
EfsPort.attrDirectMap(["signatureRadiusBonus"], stats, mod)
elif mod.item.group.name == "Sensor Dampener":
stats["type"] = "Sensor Dampener"
EfsPort.attrDirectMap(["maxTargetRangeBonus", "scanResolutionBonus"], stats, mod)
elif mod.item.group.name == "ECM":
stats["type"] = "ECM"
EfsPort.attrDirectMap([
"scanGravimetricStrengthBonus", "scanMagnetometricStrengthBonus",
"scanRadarStrengthBonus", "scanLadarStrengthBonus",
], stats, mod)
elif mod.item.group.name == "Burst Jammer":
stats["type"] = "Burst Jammer"
maxRangeDefault = mod.getModifiedItemAttr("ecmBurstRange")
EfsPort.attrDirectMap([
"scanGravimetricStrengthBonus", "scanMagnetometricStrengthBonus",
"scanRadarStrengthBonus", "scanLadarStrengthBonus",
], stats, mod)
elif mod.item.group.name == "Micro Jump Drive":
stats["type"] = "Micro Jump Drive"
EfsPort.attrDirectMap(["moduleReactivationDelay"], stats, mod)
else:
pyfalog.error("Projected module {0} lacks efs export implementation".format(mod.item.name))
if mod.getModifiedItemAttr("maxRange", None) is None:
pyfalog.error("Projected module {0} has no maxRange".format(mod.item.name))
stats["optimal"] = mod.getModifiedItemAttr("maxRange", maxRangeDefault)
stats["falloff"] = mod.getModifiedItemAttr("falloffEffectiveness", falloffDefault)
EfsPort.attrDirectMap(["duration", "capacitorNeed"], stats, mod)
projections.append(stats)
return projections
# Note that unless padTypeIDs is True all 0s will be removed from modTypeIDs in the return.
# They always are added initally for the sake of brevity, as this option may not be retained long term.
@staticmethod
def getModuleInfo(fit, padTypeIDs=False):
moduleNames = []
modTypeIDs = []
moduleNameSets = {Slot.LOW: [], Slot.MED: [], Slot.HIGH: [], Slot.RIG: [], Slot.SUBSYSTEM: []}
modTypeIDSets = {Slot.LOW: [], Slot.MED: [], Slot.HIGH: [], Slot.RIG: [], Slot.SUBSYSTEM: []}
for mod in fit.modules:
try:
if mod.item is not None:
if mod.charge is not None:
modTypeIDSets[mod.slot].append([mod.item.typeID, mod.charge.typeID])
moduleNameSets[mod.slot].append(mod.item.name + ": " + mod.charge.name)
else:
modTypeIDSets[mod.slot].append(mod.item.typeID)
moduleNameSets[mod.slot].append(mod.item.name)
else:
modTypeIDSets[mod.slot].append(0)
moduleNameSets[mod.slot].append("Empty Slot")
except:
pyfalog.error("Could not find name for module {0}".format(vars(mod)))
for modInfo in [
["High Slots:"], moduleNameSets[Slot.HIGH], ["", "Med Slots:"], moduleNameSets[Slot.MED],
["", "Low Slots:"], moduleNameSets[Slot.LOW], ["", "Rig Slots:"], moduleNameSets[Slot.RIG]
]:
moduleNames.extend(modInfo)
if len(moduleNameSets[Slot.SUBSYSTEM]) > 0:
moduleNames.extend(["", "Subsystems:"])
moduleNames.extend(moduleNameSets[Slot.SUBSYSTEM])
for slotType in [Slot.HIGH, Slot.MED, Slot.LOW, Slot.RIG, Slot.SUBSYSTEM]:
if slotType is not Slot.SUBSYSTEM or len(modTypeIDSets[slotType]) > 0:
modTypeIDs.extend([0, 0] if slotType is not Slot.HIGH else [0])
modTypeIDs.extend(modTypeIDSets[slotType])
droneNames = []
droneIDs = []
fighterNames = []
fighterIDs = []
for drone in fit.drones:
if drone.amountActive > 0:
droneIDs.append(drone.item.typeID)
droneNames.append("%s x%s" % (drone.item.name, drone.amount))
for fighter in fit.fighters:
if fighter.amountActive > 0:
fighterIDs.append(fighter.item.typeID)
fighterNames.append("%s x%s" % (fighter.item.name, fighter.amountActive))
if len(droneNames) > 0:
modTypeIDs.extend([0, 0])
modTypeIDs.extend(droneIDs)
moduleNames.extend(["", "Drones:"])
moduleNames.extend(droneNames)
if len(fighterNames) > 0:
modTypeIDs.extend([0, 0])
modTypeIDs.extend(fighterIDs)
moduleNames.extend(["", "Fighters:"])
moduleNames.extend(fighterNames)
if len(fit.implants) > 0:
modTypeIDs.extend([0, 0])
moduleNames.extend(["", "Implants:"])
for implant in fit.implants:
modTypeIDs.append(implant.item.typeID)
moduleNames.append(implant.item.name)
if len(fit.boosters) > 0:
modTypeIDs.extend([0, 0])
moduleNames.extend(["", "Boosters:"])
for booster in fit.boosters:
modTypeIDs.append(booster.item.typeID)
moduleNames.append(booster.item.name)
if len(fit.commandFits) > 0:
modTypeIDs.extend([0, 0])
moduleNames.extend(["", "Command Fits:"])
for commandFit in fit.commandFits:
modTypeIDs.append(commandFit.ship.item.typeID)
moduleNames.append(commandFit.name)
if len(fit.projectedModules) > 0:
modTypeIDs.extend([0, 0])
moduleNames.extend(["", "Projected Modules:"])
for mod in fit.projectedModules:
modTypeIDs.append(mod.item.typeID)
moduleNames.append(mod.item.name)
if fit.character.name != "All 5":
modTypeIDs.extend([0, 0, 0])
moduleNames.extend(["", "Character:"])
moduleNames.append(fit.character.name)
if padTypeIDs is not True:
modTypeIDsUnpadded = [mod for mod in modTypeIDs if mod != 0]
modTypeIDs = modTypeIDsUnpadded
return {"moduleNames": moduleNames, "modTypeIDs": modTypeIDs}
@staticmethod
def getFighterAbilityData(fighterAttr, fighter, baseRef):
baseRefDam = baseRef + "Damage"
abilityName = "RegularAttack" if baseRef == "fighterAbilityAttackMissile" else "MissileAttack"
rangeSuffix = "RangeOptimal" if baseRef == "fighterAbilityAttackMissile" else "Range"
reductionRef = baseRef if baseRef == "fighterAbilityAttackMissile" else baseRefDam
damageReductionFactor = log(fighterAttr(reductionRef + "ReductionFactor")) / log(fighterAttr(reductionRef + "ReductionSensitivity"))
damTypes = ["EM", "Therm", "Exp", "Kin"]
abBaseDamage = sum(map(lambda damType: fighterAttr(baseRefDam + damType), damTypes))
abDamage = abBaseDamage * fighterAttr(baseRefDam + "Multiplier")
return {
"name": abilityName, "volley": abDamage * fighter.amountActive, "explosionRadius": fighterAttr(baseRef + "ExplosionRadius"),
"explosionVelocity": fighterAttr(baseRef + "ExplosionVelocity"), "optimal": fighterAttr(baseRef + rangeSuffix),
"damageReductionFactor": damageReductionFactor, "rof": fighterAttr(baseRef + "Duration"),
}
@staticmethod
def getWeaponSystemData(fit):
weaponSystems = []
groups = {}
for mod in fit.modules:
if mod.dps > 0:
# Group weapon + ammo combinations that occur more than once
keystr = str(mod.itemID) + "-" + str(mod.chargeID)
if keystr in groups:
groups[keystr][1] += 1
else:
groups[keystr] = [mod, 1]
for wepGroup in groups.values():
stats = wepGroup[0]
n = wepGroup[1]
tracking = 0
maxVelocity = 0
explosionDelay = 0
damageReductionFactor = 0
explosionRadius = 0
explosionVelocity = 0
aoeFieldRange = 0
if stats.hardpoint == Hardpoint.TURRET:
tracking = stats.getModifiedItemAttr("trackingSpeed")
typeing = "Turret"
name = stats.item.name + ", " + stats.charge.name
# Bombs share most attributes with missiles despite not needing the hardpoint
elif stats.hardpoint == Hardpoint.MISSILE or "Bomb Launcher" in stats.item.name:
maxVelocity = stats.getModifiedChargeAttr("maxVelocity")
explosionDelay = stats.getModifiedChargeAttr("explosionDelay")
damageReductionFactor = stats.getModifiedChargeAttr("aoeDamageReductionFactor")
explosionRadius = stats.getModifiedChargeAttr("aoeCloudSize")
explosionVelocity = stats.getModifiedChargeAttr("aoeVelocity")
typeing = "Missile"
name = stats.item.name + ", " + stats.charge.name
elif stats.hardpoint == Hardpoint.NONE:
aoeFieldRange = stats.getModifiedItemAttr("empFieldRange")
# This also covers non-bomb weapons with dps values and no hardpoints, most notably targeted doomsdays.
typeing = "SmartBomb"
name = stats.item.name
statDict = {
"dps": stats.dps * n, "capUse": stats.capUse * n, "falloff": stats.falloff,
"type": typeing, "name": name, "optimal": stats.maxRange,
"numCharges": stats.numCharges, "numShots": stats.numShots, "reloadTime": stats.reloadTime,
"cycleTime": stats.cycleTime, "volley": stats.volley * n, "tracking": tracking,
"maxVelocity": maxVelocity, "explosionDelay": explosionDelay, "damageReductionFactor": damageReductionFactor,
"explosionRadius": explosionRadius, "explosionVelocity": explosionVelocity, "aoeFieldRange": aoeFieldRange,
"damageMultiplierBonusMax": stats.getModifiedItemAttr("damageMultiplierBonusMax"),
"damageMultiplierBonusPerCycle": stats.getModifiedItemAttr("damageMultiplierBonusPerCycle")
}
weaponSystems.append(statDict)
for drone in fit.drones:
if drone.dps[0] > 0 and drone.amountActive > 0:
droneAttr = drone.getModifiedItemAttr
# Drones are using the old tracking formula for trackingSpeed. This updates it to match turrets.
newTracking = droneAttr("trackingSpeed") / (droneAttr("optimalSigRadius") / 40000)
statDict = {
"dps": drone.dps[0], "cycleTime": drone.cycleTime, "type": "Drone",
"optimal": drone.maxRange, "name": drone.item.name, "falloff": drone.falloff,
"maxSpeed": droneAttr("maxVelocity"), "tracking": newTracking,
"volley": drone.dps[1]
}
weaponSystems.append(statDict)
for fighter in fit.fighters:
if fighter.dps[0] > 0 and fighter.amountActive > 0:
fighterAttr = fighter.getModifiedItemAttr
abilities = []
if "fighterAbilityAttackMissileDamageEM" in fighter.item.attributes.keys():
baseRef = "fighterAbilityAttackMissile"
ability = EfsPort.getFighterAbilityData(fighterAttr, fighter, baseRef)
abilities.append(ability)
if "fighterAbilityMissilesDamageEM" in fighter.item.attributes.keys():
baseRef = "fighterAbilityMissiles"
ability = EfsPort.getFighterAbilityData(fighterAttr, fighter, baseRef)
abilities.append(ability)
statDict = {
"dps": fighter.dps[0], "type": "Fighter", "name": fighter.item.name,
"maxSpeed": fighterAttr("maxVelocity"), "abilities": abilities,
"ehp": fighterAttr("shieldCapacity") / 0.8875 * fighter.amountActive,
"volley": fighter.dps[1], "signatureRadius": fighterAttr("signatureRadius")
}
weaponSystems.append(statDict)
return weaponSystems
@staticmethod
def getTestSet(setType):
def getT2ItemsWhere(additionalFilter, mustBeOffensive=False, category="Module"):
# Used to obtain a smaller subset of items while still containing examples of each group.
T2_META_LEVEL = 5
metaLevelAttrID = getAttributeInfo("metaLevel").attributeID
categoryID = getCategory(category).categoryID
result = gamedata_session.query(Item).join(ItemEffect, Group, Attribute).\
filter(
additionalFilter,
Attribute.attributeID == metaLevelAttrID,
Attribute.value == T2_META_LEVEL,
Group.categoryID == categoryID,
).all()
if mustBeOffensive:
result = filter(lambda t: t.offensive is True, result)
return list(result)
def getChargeType(item, setType):
if setType == "turret":
return str(item.attributes["chargeGroup1"].value) + "-" + str(item.attributes["chargeSize"].value)
return str(item.attributes["chargeGroup1"].value)
if setType in EfsPort.wepTestSet.keys():
return EfsPort.wepTestSet[setType]
else:
EfsPort.wepTestSet[setType] = []
modSet = EfsPort.wepTestSet[setType]
if setType == "drone":
ilist = getT2ItemsWhere(True, True, "Drone")
for item in ilist:
drone = Drone(item)
drone.amount = 1
drone.amountActive = 1
drone.itemModifiedAttributes.parent = drone
modSet.append(drone)
return modSet
turretFittedEffectID = gamedata_session.query(Effect).filter(Effect.name == "turretFitted").first().effectID
launcherFittedEffectID = gamedata_session.query(Effect).filter(Effect.name == "launcherFitted").first().effectID
if setType == "launcher":
effectFilter = ItemEffect.effectID == launcherFittedEffectID
reqOff = False
else:
effectFilter = ItemEffect.effectID == turretFittedEffectID
reqOff = True
ilist = getT2ItemsWhere(effectFilter, reqOff)
previousChargeTypes = []
# Get modules from item list
for item in ilist:
chargeType = getChargeType(item, setType)
# Only add turrets if we don"t already have one with the same size and ammo type.
if setType == "launcher" or chargeType not in previousChargeTypes:
previousChargeTypes.append(chargeType)
mod = Module(item)
modSet.append(mod)
sMkt = Market.getInstance()
# Due to typed missile damage bonuses we"ll need to add extra launchers to cover all four types.
additionalLaunchers = []
for mod in modSet:
clist = list(gamedata_session.query(Item).options().
filter(Item.groupID == mod.getModifiedItemAttr("chargeGroup1")).all())
mods = [mod]
charges = [clist[0]]
if setType == "launcher":
# We don"t want variations of missiles we already have
prevCharges = list(sMkt.getVariationsByItems(charges))
testCharges = []
for charge in clist:
if charge not in prevCharges:
testCharges.append(charge)
prevCharges += sMkt.getVariationsByItems([charge])
for c in testCharges:
charges.append(c)
additionalLauncher = Module(mod.item)
mods.append(additionalLauncher)
for i in range(len(mods)):
mods[i].charge = charges[i]
mods[i].reloadForce = True
mods[i].state = 2
if setType == "launcher" and i > 0:
additionalLaunchers.append(mods[i])
modSet += additionalLaunchers
return modSet
@staticmethod
def getWeaponBonusMultipliers(fit):
def sumDamage(attr):
totalDamage = 0
for damageType in ["emDamage", "thermalDamage", "kineticDamage", "explosiveDamage"]:
if attr(damageType) is not None:
totalDamage += attr(damageType)
return totalDamage
def getCurrentMultipliers(tf):
fitMultipliers = {}
getDroneMulti = lambda d: sumDamage(d.getModifiedItemAttr) * d.getModifiedItemAttr("damageMultiplier")
fitMultipliers["drones"] = list(map(getDroneMulti, tf.drones))
getFitTurrets = lambda f: filter(lambda mod: mod.hardpoint == Hardpoint.TURRET, f.modules)
getTurretMulti = lambda mod: mod.getModifiedItemAttr("damageMultiplier") / mod.cycleTime
fitMultipliers["turrets"] = list(map(getTurretMulti, getFitTurrets(tf)))
getFitLaunchers = lambda f: filter(lambda mod: mod.hardpoint == Hardpoint.MISSILE, f.modules)
getLauncherMulti = lambda mod: sumDamage(mod.getModifiedChargeAttr) / mod.cycleTime
fitMultipliers["launchers"] = list(map(getLauncherMulti, getFitLaunchers(tf)))
return fitMultipliers
multipliers = {"turret": 1, "launcher": 1, "droneBandwidth": 1}
drones = EfsPort.getTestSet("drone")
launchers = EfsPort.getTestSet("launcher")
turrets = EfsPort.getTestSet("turret")
for weaponTypeSet in [turrets, launchers, drones]:
for mod in weaponTypeSet:
mod.owner = fit
turrets = list(filter(lambda mod: mod.getModifiedItemAttr("damageMultiplier"), turrets))
launchers = list(filter(lambda mod: sumDamage(mod.getModifiedChargeAttr), launchers))
# Since the effect modules are fairly opaque a mock test fit is used to test the impact of traits.
# standin class used to prevent . notation causing issues when used as an arg
class standin():
pass
tf = standin()
tf.modules = HandledList(turrets + launchers)
tf.character = fit.character
tf.ship = fit.ship
tf.drones = HandledList(drones)
tf.fighters = HandledList([])
tf.boosters = HandledList([])
tf.extraAttributes = fit.extraAttributes
tf.mode = fit.mode
preTraitMultipliers = getCurrentMultipliers(tf)
for effect in fit.ship.item.effects.values():
if effect._Effect__effectModule is not None:
effect.handler(tf, tf.ship, [])
# Factor in mode effects for T3 Destroyers
if fit.mode is not None:
for effect in fit.mode.item.effects.values():
if effect._Effect__effectModule is not None:
effect.handler(tf, fit.mode, [])
if fit.ship.item.groupID == getGroup("Strategic Cruiser").ID:
subSystems = list(filter(lambda mod: mod.slot == Slot.SUBSYSTEM and mod.item, fit.modules))
for sub in subSystems:
for effect in sub.item.effects.values():
if effect._Effect__effectModule is not None:
effect.handler(tf, sub, [])
postTraitMultipliers = getCurrentMultipliers(tf)
getMaxRatio = lambda dictA, dictB, key: max(map(lambda a, b: b / a, dictA[key], dictB[key]))
multipliers["turret"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "turrets"), 6)
multipliers["launcher"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "launchers"), 6)
multipliers["droneBandwidth"] = round(getMaxRatio(preTraitMultipliers, postTraitMultipliers, "drones"), 6)
Fit.getInstance().recalc(fit)
return multipliers
@staticmethod
def getShipSize(groupID):
# Size groupings are somewhat arbitrary but allow for a more managable number of top level groupings in a tree structure.
frigateGroupNames = ["Frigate", "Shuttle", "Corvette", "Assault Frigate", "Covert Ops", "Interceptor",
"Stealth Bomber", "Electronic Attack Ship", "Expedition Frigate", "Logistics Frigate"]
destroyerGroupNames = ["Destroyer", "Interdictor", "Tactical Destroyer", "Command Destroyer"]
cruiserGroupNames = ["Cruiser", "Heavy Assault Cruiser", "Logistics", "Force Recon Ship",
"Heavy Interdiction Cruiser", "Combat Recon Ship", "Strategic Cruiser"]
bcGroupNames = ["Combat Battlecruiser", "Command Ship", "Attack Battlecruiser"]
bsGroupNames = ["Battleship", "Elite Battleship", "Black Ops", "Marauder"]
capitalGroupNames = ["Titan", "Dreadnought", "Freighter", "Carrier", "Supercarrier",
"Capital Industrial Ship", "Jump Freighter", "Force Auxiliary"]
indyGroupNames = ["Industrial", "Deep Space Transport", "Blockade Runner",
"Mining Barge", "Exhumer", "Industrial Command Ship"]
miscGroupNames = ["Capsule", "Prototype Exploration Ship"]
shipSizes = [
{"name": "Frigate", "groupIDs": map(lambda s: getGroup(s).ID, frigateGroupNames)},
{"name": "Destroyer", "groupIDs": map(lambda s: getGroup(s).ID, destroyerGroupNames)},
{"name": "Cruiser", "groupIDs": map(lambda s: getGroup(s).ID, cruiserGroupNames)},
{"name": "Battlecruiser", "groupIDs": map(lambda s: getGroup(s).ID, bcGroupNames)},
{"name": "Battleship", "groupIDs": map(lambda s: getGroup(s).ID, bsGroupNames)},
{"name": "Capital", "groupIDs": map(lambda s: getGroup(s).ID, capitalGroupNames)},
{"name": "Industrial", "groupIDs": map(lambda s: getGroup(s).ID, indyGroupNames)},
{"name": "Misc", "groupIDs": map(lambda s: getGroup(s).ID, miscGroupNames)}
]
for size in shipSizes:
if groupID in size["groupIDs"]:
return size["name"]
sizeNotFoundMsg = "ShipSize not found for groupID: " + str(groupID)
return sizeNotFoundMsg
@staticmethod
def exportEfs(fit, typeNotFitFlag):
sFit = Fit.getInstance()
includeShipTypeData = typeNotFitFlag > 0
if includeShipTypeData:
fitName = fit.name
else:
fitName = fit.ship.name + ": " + fit.name
pyfalog.info("Creating Eve Fleet Simulator data for: " + fit.name)
fitModAttr = fit.ship.getModifiedItemAttr
propData = EfsPort.getPropData(fit, sFit)
mwdPropSpeed = fit.maxSpeed
if includeShipTypeData:
mwdPropSpeed = EfsPort.getT2MwdSpeed(fit, sFit)
projections = EfsPort.getOutgoingProjectionData(fit)
modInfo = EfsPort.getModuleInfo(fit)
moduleNames = modInfo["moduleNames"]
modTypeIDs = modInfo["modTypeIDs"]
weaponSystems = EfsPort.getWeaponSystemData(fit)
turretSlots = fitModAttr("turretSlotsLeft") if fitModAttr("turretSlotsLeft") is not None else 0
launcherSlots = fitModAttr("launcherSlotsLeft") if fitModAttr("launcherSlotsLeft") is not None else 0
droneBandwidth = fitModAttr("droneBandwidth") if fitModAttr("droneBandwidth") is not None else 0
weaponBonusMultipliers = EfsPort.getWeaponBonusMultipliers(fit)
effectiveTurretSlots = round(turretSlots * weaponBonusMultipliers["turret"], 2)
effectiveLauncherSlots = round(launcherSlots * weaponBonusMultipliers["launcher"], 2)
effectiveDroneBandwidth = round(droneBandwidth * weaponBonusMultipliers["droneBandwidth"], 2)
# Assume a T2 siege module for dreads
if fit.ship.item.group.name == "Dreadnought":
effectiveTurretSlots *= 9.4
effectiveLauncherSlots *= 15
hullResonance = {
"exp": fitModAttr("explosiveDamageResonance"), "kin": fitModAttr("kineticDamageResonance"),
"therm": fitModAttr("thermalDamageResonance"), "em": fitModAttr("emDamageResonance")
}
armorResonance = {
"exp": fitModAttr("armorExplosiveDamageResonance"), "kin": fitModAttr("armorKineticDamageResonance"),
"therm": fitModAttr("armorThermalDamageResonance"), "em": fitModAttr("armorEmDamageResonance")
}
shieldResonance = {
"exp": fitModAttr("shieldExplosiveDamageResonance"), "kin": fitModAttr("shieldKineticDamageResonance"),
"therm": fitModAttr("shieldThermalDamageResonance"), "em": fitModAttr("shieldEmDamageResonance")
}
resonance = {"hull": hullResonance, "armor": armorResonance, "shield": shieldResonance}
shipSize = EfsPort.getShipSize(fit.ship.item.groupID)
try:
dataDict = {
"name": fitName, "ehp": fit.ehp, "droneDPS": fit.droneDPS,
"droneVolley": fit.droneVolley, "hp": fit.hp, "maxTargets": fit.maxTargets,
"maxSpeed": fit.maxSpeed, "weaponVolley": fit.weaponVolley, "totalVolley": fit.totalVolley,
"maxTargetRange": fit.maxTargetRange, "scanStrength": fit.scanStrength,
"weaponDPS": fit.weaponDPS, "alignTime": fit.alignTime, "signatureRadius": fitModAttr("signatureRadius"),
"weapons": weaponSystems, "scanRes": fitModAttr("scanResolution"),
"capUsed": fit.capUsed, "capRecharge": fit.capRecharge,
"rigSlots": fitModAttr("rigSlots"), "lowSlots": fitModAttr("lowSlots"),
"midSlots": fitModAttr("medSlots"), "highSlots": fitModAttr("hiSlots"),
"turretSlots": fitModAttr("turretSlotsLeft"), "launcherSlots": fitModAttr("launcherSlotsLeft"),
"powerOutput": fitModAttr("powerOutput"), "cpuOutput": fitModAttr("cpuOutput"),
"rigSize": fitModAttr("rigSize"), "effectiveTurrets": effectiveTurretSlots,
"effectiveLaunchers": effectiveLauncherSlots, "effectiveDroneBandwidth": effectiveDroneBandwidth,
"resonance": resonance, "typeID": fit.shipID, "groupID": fit.ship.item.groupID, "shipSize": shipSize,
"droneControlRange": fitModAttr("droneControlRange"), "mass": fitModAttr("mass"),
"unpropedSpeed": propData["unpropedSpeed"], "unpropedSig": propData["unpropedSig"],
"usingMWD": propData["usingMWD"], "mwdPropSpeed": mwdPropSpeed, "projections": projections,
"modTypeIDs": modTypeIDs, "moduleNames": moduleNames,
"pyfaVersion": pyfaVersion, "efsExportVersion": EfsPort.version
}
except TypeError:
pyfalog.error("Error parsing fit:" + str(fit))
pyfalog.error(TypeError)
dataDict = {"name": fitName + "Fit could not be correctly parsed"}
export = json.dumps(dataDict, skipkeys=True)
return export

View File

@@ -208,11 +208,11 @@ class Fit(FitDeprecated):
# error during the command loop
refreshFits = set()
for projection in list(fit.projectedOnto.values()):
if projection.victim_fit != fit and projection.victim_fit in eos.db.saveddata_session: # GH issue #359
if projection.victim_fit and projection.victim_fit != fit and projection.victim_fit in eos.db.saveddata_session: # GH issue #359
refreshFits.add(projection.victim_fit)
for booster in list(fit.boostedOnto.values()):
if booster.boosted_fit != fit and booster.boosted_fit in eos.db.saveddata_session: # GH issue #359
if booster.boosted_fit and booster.boosted_fit != fit and booster.boosted_fit in eos.db.saveddata_session: # GH issue #359
refreshFits.add(booster.boosted_fit)
eos.db.remove(fit)

View File

@@ -277,15 +277,8 @@ class Market(object):
# Dictionary of items with forced market group (service assumes they have no
# market group assigned in db, otherwise they'll appear in both original and forced groups)
self.ITEMS_FORCEDMARKETGROUP = {
"Advanced Cerebral Accelerator" : 977, # Implants & Boosters > Booster
"Civilian Damage Control" : 615, # Ship Equipment > Hull & Armor > Damage Controls
"Civilian EM Ward Field" : 1695,
# Ship Equipment > Shield > Shield Hardeners > EM Shield Hardeners
"Civilian Explosive Deflection Field" : 1694,
# Ship Equipment > Shield > Shield Hardeners > Explosive Shield Hardeners
"Advanced Cerebral Accelerator" : 2487, # Implants & Boosters > Booster > Cerebral Accelerators
"Civilian Hobgoblin" : 837, # Drones > Combat Drones > Light Scout Drones
"Civilian Kinetic Deflection Field" : 1693,
# Ship Equipment > Shield > Shield Hardeners > Kinetic Shield Hardeners
"Civilian Light Missile Launcher" : 640,
# Ship Equipment > Turrets & Bays > Missile Launchers > Light Missile Launchers
"Civilian Scourge Light Missile" : 920,
@@ -293,8 +286,6 @@ class Market(object):
"Civilian Small Remote Armor Repairer" : 1059,
# Ship Equipment > Hull & Armor > Remote Armor Repairers > Small
"Civilian Small Remote Shield Booster" : 603, # Ship Equipment > Shield > Remote Shield Boosters > Small
"Civilian Stasis Webifier" : 683, # Ship Equipment > Electronic Warfare > Stasis Webifiers
"Civilian Warp Disruptor" : 1935, # Ship Equipment > Electronic Warfare > Warp Disruptors
"Hardwiring - Zainou 'Sharpshooter' ZMX10" : 1493,
# Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06
"Hardwiring - Zainou 'Sharpshooter' ZMX100" : 1493,
@@ -307,11 +298,9 @@ class Market(object):
# Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06
"Hardwiring - Zainou 'Sharpshooter' ZMX1100": 1493,
# Implants & Boosters > Implants > Skill Hardwiring > Missile Implants > Implant Slot 06
"Nugoehuvi Synth Blue Pill Booster" : 977, # Implants & Boosters > Booster
"Prototype Cerebral Accelerator" : 977, # Implants & Boosters > Booster
"Prototype Cerebral Accelerator" : 2487, # Implants & Boosters > Booster > Cerebral Accelerators
"Prototype Iris Probe Launcher" : 712, # Ship Equipment > Turrets & Bays > Scan Probe Launchers
"Shadow" : 1310, # Drones > Combat Drones > Fighter Bombers
"Standard Cerebral Accelerator" : 977, # Implants & Boosters > Booster
"Standard Cerebral Accelerator" : 2487, # Implants & Boosters > Booster > Cerebral Accelerators
}
self.ITEMS_FORCEDMARKETGROUP_R = self.__makeRevDict(self.ITEMS_FORCEDMARKETGROUP)
@@ -538,7 +527,7 @@ class Market(object):
categories = ['Drone', 'Fighter', 'Implant']
for item in items:
if item.category.ID == 20: # Implants and Boosters
if item.category.ID == 20 and item.group.ID != 303: # Implants not Boosters
implant_remove_list = set()
implant_remove_list.add("Low-Grade ")
implant_remove_list.add("Low-grade ")
@@ -552,15 +541,6 @@ class Market(object):
implant_remove_list.add(" - Elite")
implant_remove_list.add(" - Improved")
implant_remove_list.add(" - Standard")
implant_remove_list.add("Copper ")
implant_remove_list.add("Gold ")
implant_remove_list.add("Silver ")
implant_remove_list.add("Advanced ")
implant_remove_list.add("Improved ")
implant_remove_list.add("Prototype ")
implant_remove_list.add("Standard ")
implant_remove_list.add("Strong ")
implant_remove_list.add("Synth ")
for implant_prefix in ("-6", "-7", "-8", "-9", "-10"):
for i in range(50):
@@ -596,6 +576,16 @@ class Market(object):
if trimmed_variations_list:
variations_list = trimmed_variations_list
# If the items are boosters then filter variations to only include boosters for the same slot.
BOOSTER_GROUP_ID = 303
if all(map(lambda i: i.group.ID == BOOSTER_GROUP_ID, items)) and len(items) > 0:
# 'boosterness' is the database's attribute name for Booster Slot
reqSlot = next(items.__iter__()).getAttribute('boosterness')
# If the item and it's variation both have a marketGroupID it should match for the variation to be considered valid.
marketGroupID = [next(filter(None, map(lambda i: i.marketGroupID, items)), None), None]
matchSlotAndMktGrpID = lambda v: v.getAttribute('boosterness') == reqSlot and v.marketGroupID in marketGroupID
variations_list = list(filter(matchSlotAndMktGrpID, variations_list))
variations.update(variations_list)
return variations
@@ -657,6 +647,12 @@ class Market(object):
def marketGroupHasTypesCheck(self, mg):
"""If market group has any items, return true"""
if mg and mg.ID in self.ITEMS_FORCEDMARKETGROUP_R:
# This shouldn't occur normally but makes errors more mild when ITEMS_FORCEDMARKETGROUP is outdated.
if len(mg.children) > 0 and len(mg.items) == 0:
pyfalog.error(("Market group \"{0}\" contains no items and has children. "
"ITEMS_FORCEDMARKETGROUP is likely outdated and will need to be "
"updated for {1} to display correctly.").format(mg, self.ITEMS_FORCEDMARKETGROUP_R[mg.ID]))
return False
return True
elif len(mg.items) > 0:
return True

View File

@@ -306,8 +306,11 @@ class Port(object):
fit.character = sFit.character
fit.damagePattern = sFit.pattern
fit.targetResists = sFit.targetResists
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
if len(fit.implants) > 0:
fit.implantLocation = ImplantLocation.FIT
else:
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
db.save(fit)
# IDs.append(fit.ID)
if iportuser: # Pulse
@@ -339,8 +342,11 @@ class Port(object):
fit.character = sFit.character
fit.damagePattern = sFit.pattern
fit.targetResists = sFit.targetResists
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
if len(fit.implants) > 0:
fit.implantLocation = ImplantLocation.FIT
else:
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
db.save(fit)
return fits