diff --git a/config.py b/config.py
index 750888659..7afe652f5 100644
--- a/config.py
+++ b/config.py
@@ -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
diff --git a/eos/effects/boostershieldcapacitypenalty.py b/eos/effects/boostershieldcapacitypenalty.py
index ade1336d0..9258da2c5 100644
--- a/eos/effects/boostershieldcapacitypenalty.py
+++ b/eos/effects/boostershieldcapacitypenalty.py
@@ -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
diff --git a/eos/effects/covertopsandreconopscloakmoduledelaybonus.py b/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
index a808a2650..7ea8e593d 100644
--- a/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
+++ b/eos/effects/covertopsandreconopscloakmoduledelaybonus.py
@@ -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)
diff --git a/eos/effects/covertopscloakcpupercentbonus1.py b/eos/effects/covertopscloakcpupercentbonus1.py
index 703b37189..e34450131 100644
--- a/eos/effects/covertopscloakcpupercentbonus1.py
+++ b/eos/effects/covertopscloakcpupercentbonus1.py
@@ -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"
diff --git a/eos/effects/covertopswarpresistance.py b/eos/effects/covertopswarpresistance.py
new file mode 100644
index 000000000..1ab7538c1
--- /dev/null
+++ b/eos/effects/covertopswarpresistance.py
@@ -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")
diff --git a/eos/effects/cynosuraldurationbonus.py b/eos/effects/cynosuraldurationbonus.py
index cb306dded..4f78ea09e 100644
--- a/eos/effects/cynosuraldurationbonus.py
+++ b/eos/effects/cynosuraldurationbonus.py
@@ -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"
diff --git a/eos/effects/cynosuraltheoryconsumptionbonus.py b/eos/effects/cynosuraltheoryconsumptionbonus.py
index 217374cee..d67c969f8 100644
--- a/eos/effects/cynosuraltheoryconsumptionbonus.py
+++ b/eos/effects/cynosuraltheoryconsumptionbonus.py
@@ -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"
diff --git a/eos/effects/elitebonuscoveropsscanprobestrength2.py b/eos/effects/elitebonuscoveropsscanprobestrength2.py
index 8b922cee1..77feea510 100644
--- a/eos/effects/elitebonuscoveropsscanprobestrength2.py
+++ b/eos/effects/elitebonuscoveropsscanprobestrength2.py
@@ -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"
diff --git a/eos/effects/elitebonusmaxdmgmultibonusadd.py b/eos/effects/elitebonusmaxdmgmultibonusadd.py
new file mode 100644
index 000000000..7e9a5cc29
--- /dev/null
+++ b/eos/effects/elitebonusmaxdmgmultibonusadd.py
@@ -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")
diff --git a/eos/effects/elitebonusreconmaxdmgmultimaxhpt.py b/eos/effects/elitebonusreconmaxdmgmultimaxhpt.py
new file mode 100644
index 000000000..1350d5a93
--- /dev/null
+++ b/eos/effects/elitebonusreconmaxdmgmultimaxhpt.py
@@ -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")
diff --git a/eos/effects/elitebonusreconscanprobestrength2.py b/eos/effects/elitebonusreconscanprobestrength2.py
new file mode 100644
index 000000000..55db46631
--- /dev/null
+++ b/eos/effects/elitebonusreconscanprobestrength2.py
@@ -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")
diff --git a/eos/effects/minigamevirusstrengthbonus.py b/eos/effects/minigamevirusstrengthbonus.py
index 2023d0407..0e60325db 100644
--- a/eos/effects/minigamevirusstrengthbonus.py
+++ b/eos/effects/minigamevirusstrengthbonus.py
@@ -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
diff --git a/eos/effects/reconshipcloakcpubonus1.py b/eos/effects/reconshipcloakcpubonus1.py
index 8f63a28f2..e7c05671c 100644
--- a/eos/effects/reconshipcloakcpubonus1.py
+++ b/eos/effects/reconshipcloakcpubonus1.py
@@ -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"
diff --git a/eos/effects/shipbonusneutcapneedrolebonus2.py b/eos/effects/shipbonusneutcapneedrolebonus2.py
index 324a01c66..b48e784ad 100644
--- a/eos/effects/shipbonusneutcapneedrolebonus2.py
+++ b/eos/effects/shipbonusneutcapneedrolebonus2.py
@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
# Ship: Leshak
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonuspctdamagepc1.py b/eos/effects/shipbonuspctdamagepc1.py
index 50db0c467..06e686ab2 100644
--- a/eos/effects/shipbonuspctdamagepc1.py
+++ b/eos/effects/shipbonuspctdamagepc1.py
@@ -1,6 +1,7 @@
# shipbonusPCTDamagePC1
#
# Used by:
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonuspctdamagepf1.py b/eos/effects/shipbonuspctdamagepf1.py
index 7fd636e18..59a1071e2 100644
--- a/eos/effects/shipbonuspctdamagepf1.py
+++ b/eos/effects/shipbonuspctdamagepf1.py
@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
type = "passive"
diff --git a/eos/effects/shipbonuspctoptimalpf2.py b/eos/effects/shipbonuspctoptimalpf2.py
index 424b0d282..7403b2146 100644
--- a/eos/effects/shipbonuspctoptimalpf2.py
+++ b/eos/effects/shipbonuspctoptimalpf2.py
@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
type = "passive"
diff --git a/eos/effects/shipbonuspcttrackingpc2.py b/eos/effects/shipbonuspcttrackingpc2.py
index 69224ac73..4914d290e 100644
--- a/eos/effects/shipbonuspcttrackingpc2.py
+++ b/eos/effects/shipbonuspcttrackingpc2.py
@@ -1,6 +1,7 @@
# shipbonusPCTTrackingPC2
#
# Used by:
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonusremoterepcapneedrolebonus2.py b/eos/effects/shipbonusremoterepcapneedrolebonus2.py
index d2b7f5ea7..64df7fa9e 100644
--- a/eos/effects/shipbonusremoterepcapneedrolebonus2.py
+++ b/eos/effects/shipbonusremoterepcapneedrolebonus2.py
@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
# Ship: Leshak
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonusremoterepmaxrangerolebonus1.py b/eos/effects/shipbonusremoterepmaxrangerolebonus1.py
index cdcc2773d..fc2fc3b14 100644
--- a/eos/effects/shipbonusremoterepmaxrangerolebonus1.py
+++ b/eos/effects/shipbonusremoterepmaxrangerolebonus1.py
@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
# Ship: Leshak
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonussmartbombcapneedrolebonus2.py b/eos/effects/shipbonussmartbombcapneedrolebonus2.py
index 611e9822b..07eb85b54 100644
--- a/eos/effects/shipbonussmartbombcapneedrolebonus2.py
+++ b/eos/effects/shipbonussmartbombcapneedrolebonus2.py
@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
+# Ship: Hydra
# Ship: Leshak
+# Ship: Tiamat
# Ship: Vedmak
type = "passive"
diff --git a/eos/effects/shipbonussurveyprobeexplosiondelayskillsurveycovertops3.py b/eos/effects/shipbonussurveyprobeexplosiondelayskillsurveycovertops3.py
index db215459d..4fc7124c5 100644
--- a/eos/effects/shipbonussurveyprobeexplosiondelayskillsurveycovertops3.py
+++ b/eos/effects/shipbonussurveyprobeexplosiondelayskillsurveycovertops3.py
@@ -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"
diff --git a/eve.db b/eve.db
index ecab30628..9727bd3fb 100644
Binary files a/eve.db and b/eve.db differ
diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py
index a70bea814..b74c93b3f 100644
--- a/gui/copySelectDialog.py
+++ b/gui/copySelectDialog.py
@@ -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)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 5dd119db0..a4befdeda 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -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()
diff --git a/imgs/icons/10160.png b/imgs/icons/10160.png
new file mode 100644
index 000000000..ce1558e0b
Binary files /dev/null and b/imgs/icons/10160.png differ
diff --git a/imgs/icons/10851.png b/imgs/icons/10851.png
new file mode 100644
index 000000000..30642ceb3
Binary files /dev/null and b/imgs/icons/10851.png differ
diff --git a/imgs/icons/21785.png b/imgs/icons/21785.png
new file mode 100644
index 000000000..d52e7c71d
Binary files /dev/null and b/imgs/icons/21785.png differ
diff --git a/imgs/icons/22029.png b/imgs/icons/22029.png
new file mode 100644
index 000000000..a5e2f7eca
Binary files /dev/null and b/imgs/icons/22029.png differ
diff --git a/imgs/icons/22030.png b/imgs/icons/22030.png
new file mode 100644
index 000000000..627468bb9
Binary files /dev/null and b/imgs/icons/22030.png differ
diff --git a/imgs/icons/22031.png b/imgs/icons/22031.png
new file mode 100644
index 000000000..f9ffc890b
Binary files /dev/null and b/imgs/icons/22031.png differ
diff --git a/imgs/icons/22034.png b/imgs/icons/22034.png
new file mode 100644
index 000000000..aa3e8941f
Binary files /dev/null and b/imgs/icons/22034.png differ
diff --git a/imgs/icons/22036.png b/imgs/icons/22036.png
new file mode 100644
index 000000000..7de00e638
Binary files /dev/null and b/imgs/icons/22036.png differ
diff --git a/imgs/icons/22041.png b/imgs/icons/22041.png
new file mode 100644
index 000000000..560421e25
Binary files /dev/null and b/imgs/icons/22041.png differ
diff --git a/imgs/renders/21344.png b/imgs/renders/21344.png
new file mode 100644
index 000000000..f45f26972
Binary files /dev/null and b/imgs/renders/21344.png differ
diff --git a/imgs/renders/21362.png b/imgs/renders/21362.png
new file mode 100644
index 000000000..f6dff6ea2
Binary files /dev/null and b/imgs/renders/21362.png differ
diff --git a/imgs/renders/21405.png b/imgs/renders/21405.png
new file mode 100644
index 000000000..55a6cc3a5
Binary files /dev/null and b/imgs/renders/21405.png differ
diff --git a/imgs/renders/21406.png b/imgs/renders/21406.png
new file mode 100644
index 000000000..e9d8d8610
Binary files /dev/null and b/imgs/renders/21406.png differ
diff --git a/scripts/icons_update.py b/scripts/icons_update.py
index 7205e3e08..0ad4c5359 100644
--- a/scripts/icons_update.py
+++ b/scripts/icons_update.py
@@ -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:
diff --git a/scripts/jsonToSql.py b/scripts/jsonToSql.py
index 3cb2caf0a..2d1b3ed01 100755
--- a/scripts/jsonToSql.py
+++ b/scripts/jsonToSql.py
@@ -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 = "{}".format(sectionData["header"])
+ headerText = '{}'.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 = "
\n".join(sectionLines)
+ sectionLine = '
\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 = "
\n
\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 = '
\n
\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)
diff --git a/service/efsPort.py b/service/efsPort.py
new file mode 100755
index 000000000..d1dd394d9
--- /dev/null
+++ b/service/efsPort.py
@@ -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
diff --git a/service/fit.py b/service/fit.py
index 1f4b417dd..ae5a59eb8 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -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)
diff --git a/service/market.py b/service/market.py
index 895cb2e98..545b1a66f 100644
--- a/service/market.py
+++ b/service/market.py
@@ -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
diff --git a/service/port.py b/service/port.py
index a0dd0a89c..e659a5899 100644
--- a/service/port.py
+++ b/service/port.py
@@ -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