diff --git a/config.py b/config.py
index fa073ad9d..d10dda270 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/entosislink.py b/eos/effects/entosislink.py
index a28b8da2d..11b5faa28 100644
--- a/eos/effects/entosislink.py
+++ b/eos/effects/entosislink.py
@@ -7,3 +7,9 @@ type = "active"
def handler(fit, module, context):
fit.ship.forceItemAttr("disallowAssistance", module.getModifiedItemAttr("disallowAssistance"))
+ for scanType in ("Gravimetric", "Magnetometric", "Radar", "Ladar"):
+ fit.ship.boostItemAttr(
+ "scan{}Strength".format(scanType),
+ module.getModifiedItemAttr("scan{}StrengthPercent".format(scanType)),
+ stackingPenalties=True
+ )
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/eos/saveddata/fit.py b/eos/saveddata/fit.py
index 8f5cc2b36..dc183d3c0 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -685,15 +685,13 @@ class Fit(object):
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Remote Armor Repair Systems"),
"armorDamageAmount", value, stackingPenalties=True)
- if warfareBuffID == 88: # AOE_Beacon_filament_cloud_shield_booster
- self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation") or
- mod.item.requiresSkill("Shield Emission Systems"),
- "capacitorNeed", value, stackingPenalties=True)
+ if warfareBuffID == 88: # AOE_Beacon_filament_cloud_shield_booster_shield_bonus
+ self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation"),
+ "shieldBonus", value, stackingPenalties=True)
- if warfareBuffID == 89: # AOE_Beacon_filament_cloud_ancillary_charge_usage
- self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation") or
- mod.item.requiresSkill("Shield Emission Systems"),
- "chargeRate", value, stackingPenalties=True)
+ if warfareBuffID == 89: # AOE_Beacon_filament_cloud_shield_booster_duration
+ self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation"),
+ "duration", value, stackingPenalties=True)
# Abysmal Weather Effects
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/builtinContextMenus/moduleAmmoPicker.py b/gui/builtinContextMenus/moduleAmmoPicker.py
index 93ff48754..38a498eb0 100644
--- a/gui/builtinContextMenus/moduleAmmoPicker.py
+++ b/gui/builtinContextMenus/moduleAmmoPicker.py
@@ -195,7 +195,7 @@ class ModuleAmmoPicker(ContextMenu):
type_ = currType
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
- bitmap = BitmapLoader.getBitmap("%s_small" % type, "gui")
+ bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
if bitmap is not None:
item.SetBitmap(bitmap)
diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py
index 9e9ca53af..1561ed58b 100644
--- a/gui/builtinItemStatsViews/itemCompare.py
+++ b/gui/builtinItemStatsViews/itemCompare.py
@@ -125,11 +125,11 @@ class ItemCompare(wx.Panel):
# Remember to reduce by 1, because the attrs array
# starts at 0 while the list has the item name as column 0.
attr = str(list(self.attrs.keys())[sort - 1])
- func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None
+ func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else 0.0
except IndexError:
# Clicked on a column that's not part of our array (price most likely)
self.sortReverse = False
- func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else None
+ func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else 0.0
self.items = sorted(self.items, key=func, reverse=self.sortReverse)
diff --git a/gui/builtinPreferenceViews/pyfaEnginePreferences.py b/gui/builtinPreferenceViews/pyfaEnginePreferences.py
index 815e1b124..c41921393 100644
--- a/gui/builtinPreferenceViews/pyfaEnginePreferences.py
+++ b/gui/builtinPreferenceViews/pyfaEnginePreferences.py
@@ -4,8 +4,10 @@ import wx
from service.fit import Fit
from gui.bitmap_loader import BitmapLoader
+import gui.globalEvents as GE
from gui.preferenceView import PreferenceView
from service.settings import EOSSettings
+import gui.mainFrame
logger = logging.getLogger(__name__)
@@ -21,6 +23,7 @@ class PFFittingEnginePref(PreferenceView):
# noinspection PyAttributeOutsideInit
def populatePanel(self, panel):
+ self.mainFrame = gui.mainFrame.MainFrame.getInstance()
mainSizer = wx.BoxSizer(wx.VERTICAL)
@@ -98,6 +101,9 @@ class PFFittingEnginePref(PreferenceView):
def OnCBGlobalForceReloadStateChange(self, event):
self.sFit.serviceFittingOptions["useGlobalForceReload"] = self.cbGlobalForceReload.GetValue()
+ fitID = self.mainFrame.getActiveFit()
+ self.sFit.refreshFit(fitID)
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
def OnCBStrictSkillLevelsChange(self, event):
self.engine_settings.set("strictSkillLevels", self.cbStrictSkillLevels.GetValue())
diff --git a/gui/builtinShipBrowser/sfBrowserItem.py b/gui/builtinShipBrowser/sfBrowserItem.py
index 16b7a4f24..bb30a040b 100644
--- a/gui/builtinShipBrowser/sfBrowserItem.py
+++ b/gui/builtinShipBrowser/sfBrowserItem.py
@@ -1,6 +1,7 @@
# noinspection PyPackageRequirements
import wx
import gui.utils.draw as drawUtils
+import gui.mainFrame
SB_ITEM_NORMAL = 0
SB_ITEM_SELECTED = 1
@@ -245,6 +246,7 @@ class SFBrowserItem(wx.Window):
self.highlighted = False
self.selected = False
self.bkBitmap = None
+ self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.canBeDragged = False
@@ -311,6 +313,10 @@ class SFBrowserItem(wx.Window):
self.canBeDragged = mode
def OnLeftUp(self, event):
+ if self.mainFrame.supress_left_up:
+ wx.Yield()
+ self.mainFrame.supress_left_up = False
+ return
if self.HasCapture():
self.ReleaseMouse()
diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py
index 728376bef..ad99b8fb3 100644
--- a/gui/builtinViewColumns/price.py
+++ b/gui/builtinViewColumns/price.py
@@ -45,21 +45,21 @@ class Price(ViewColumn):
if stuff.isEmpty:
return ""
- price = stuff.item.price.price
+ price = stuff.item.price
- if not price:
- return ""
+ if not price or not price.isValid:
+ return False
if isinstance(stuff, Drone) or isinstance(stuff, Cargo):
- price *= stuff.amount
+ price.price *= stuff.amount
- return formatAmount(price, 3, 3, 9, currency=True)
+ return formatAmount(price.price, 3, 3, 9, currency=True)
def delayedText(self, mod, display, colItem):
sPrice = ServicePrice.getInstance()
def callback(item):
- price = item.item.price
+ price = item[0]
text = formatAmount(price.price, 3, 3, 9, currency=True) if price.price else ""
if price.failed:
text += " (!)"
diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py
index d6bf4ae0c..458aa4ff2 100644
--- a/gui/builtinViews/fittingView.py
+++ b/gui/builtinViews/fittingView.py
@@ -477,7 +477,7 @@ class FittingView(d.Display):
return
if getattr(mod2, "modPosition") is not None:
- if clone and mod2.isEmpty:
+ if clone and mod2.isEmpty and mod1.getModifiedItemAttr("maxGroupFitted", 0) < 1.0:
sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
else:
sFit.swapModules(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition)
diff --git a/gui/chrome_tabs.py b/gui/chrome_tabs.py
index 521ac6168..95d79415d 100644
--- a/gui/chrome_tabs.py
+++ b/gui/chrome_tabs.py
@@ -552,24 +552,6 @@ class _TabRenderer:
bmp,
self.left_width + self.padding - bmp.GetWidth() / 2,
(height - bmp.GetHeight()) / 2)
- text_start = self.left_width + self.padding + bmp.GetWidth() / 2
- else:
- text_start = self.left_width
-
- mdc.SetFont(self.font)
-
- maxsize = self.tab_width \
- - text_start \
- - self.right_width \
- - self.padding * 4
- color = self.selected_color if self.selected else self.inactive_color
-
- mdc.SetTextForeground(color_utils.GetSuitable(color, 1))
-
- # draw text (with no ellipses)
- text = draw.GetPartialText(mdc, self.text, maxsize, "")
- tx, ty = mdc.GetTextExtent(text)
- mdc.DrawText(text, text_start + self.padding, height / 2 - ty / 2)
# draw close button
if self.closeable:
@@ -596,6 +578,30 @@ class _TabRenderer:
bmp = wx.Bitmap(img)
self.tab_bitmap = bmp
+ # We draw the text separately in order to draw it directly on the native DC, rather than a memory one, because
+ # drawing text on a memory DC draws it blurry on HD/Retina screens
+ def DrawText(self, dc):
+ height = self.tab_height
+ dc.SetFont(self.font)
+
+ if self.tab_img:
+ text_start = self.left_width + self.padding + self.tab_img.GetWidth() / 2
+ else:
+ text_start = self.left_width
+
+ maxsize = self.tab_width \
+ - text_start \
+ - self.right_width \
+ - self.padding * 4
+ color = self.selected_color if self.selected else self.inactive_color
+
+ dc.SetTextForeground(color_utils.GetSuitable(color, 1))
+
+ # draw text (with no ellipses)
+ text = draw.GetPartialText(dc, self.text, maxsize, "")
+ tx, ty = dc.GetTextExtent(text)
+ dc.DrawText(text, text_start + self.padding, height / 2 - ty / 2)
+
def __repr__(self):
return "_TabRenderer(text={}, disabled={}) at {}".format(
self.text, self.disabled, hex(id(self))
@@ -1145,6 +1151,10 @@ class _TabsContainer(wx.Panel):
img = img.AdjustChannels(1, 1, 1, 0.85)
bmp = wx.Bitmap(img)
mdc.DrawBitmap(bmp, posx, posy, True)
+
+ mdc.SetDeviceOrigin(posx, posy)
+ tab.DrawText(mdc)
+ mdc.SetDeviceOrigin(0, 0)
else:
selected = tab
@@ -1164,6 +1174,10 @@ class _TabsContainer(wx.Panel):
mdc.DrawBitmap(bmp, posx, posy, True)
+ mdc.SetDeviceOrigin(posx, posy)
+ selected.DrawText(mdc)
+ mdc.SetDeviceOrigin(0, 0)
+
def OnErase(self, event):
pass
diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py
index b74c93b3f..9f5291026 100644
--- a/gui/copySelectDialog.py
+++ b/gui/copySelectDialog.py
@@ -41,7 +41,7 @@ class CopySelectDialog(wx.Dialog):
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.copyFormatEsi: "A JSON format used for ESI",
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,
diff --git a/gui/errorDialog.py b/gui/errorDialog.py
index 706e3c5a3..d9112d4b8 100644
--- a/gui/errorDialog.py
+++ b/gui/errorDialog.py
@@ -26,6 +26,7 @@ import traceback
import config
from logbook import Logger
from service.prereqsCheck import version_block
+import datetime
pyfalog = Logger(__name__)
@@ -63,6 +64,11 @@ class ErrorFrame(wx.Frame):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600),
style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.STAY_ON_TOP)
+ from eos.config import gamedata_version, gamedata_date
+
+ time = datetime.datetime.fromtimestamp(int(gamedata_date)).strftime('%Y-%m-%d %H:%M:%S')
+ version = "pyfa v" + config.getVersion() + '\nEVE Data Version: {} ({})\n\n'.format(gamedata_version, time) # gui.aboutData.versionString
+
desc = "pyfa has experienced an unexpected issue. Below is a message that contains crucial\n" \
"information about how this was triggered. Please contact the developers with the\n" \
"information provided through the EVE Online forums or file a GitHub issue."
@@ -97,7 +103,7 @@ class ErrorFrame(wx.Frame):
# mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5)
- self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, version_block.strip(), wx.DefaultPosition,
+ self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, version + version_block.strip(), wx.DefaultPosition,
(-1, 400), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP)
self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL))
mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 9bd02feee..e5f9fd830 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -155,6 +155,7 @@ class MainFrame(wx.Frame):
pyfalog.debug("Initialize MainFrame")
self.title = title
wx.Frame.__init__(self, None, wx.ID_ANY, self.title)
+ self.supress_left_up = False
MainFrame.__instance = self
@@ -433,6 +434,7 @@ class MainFrame(wx.Frame):
style=wx.FD_SAVE,
defaultFile=defaultFile)
if dlg.ShowModal() == wx.ID_OK:
+ self.supress_left_up = True
format_ = dlg.GetFilterIndex()
path = dlg.GetPath()
if format_ == 0:
@@ -977,6 +979,7 @@ class MainFrame(wx.Frame):
)
if dlg.ShowModal() == wx.ID_OK:
+ self.supress_left_up = True
self.waitDialog = wx.BusyInfo("Importing Character...")
sCharacter = Character.getInstance()
sCharacter.importCharacter(dlg.GetPaths(), self.importCharacterCallback)
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..add0fe957 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': 'displayName'
},
- "dgmeffects": {
- "displayName_en-us": "displayName",
- "description_en-us": "description"
+ 'dgmeffects': {
+ 'displayName': 'displayName',
+ 'description': 'description'
},
- "dgmunits": {
- "displayName_en-us": "displayName"
+ 'dgmunits': {
+ 'displayName': 'displayName'
},
#icons???
- "evecategories": {
- "categoryName_en-us": "categoryName"
+ 'evecategories': {
+ 'categoryName': 'categoryName'
},
- "evegroups": {
- "groupName_en-us": "groupName"
+ 'evegroups': {
+ 'groupName': 'groupName'
},
- "invmetagroups": {
- "metaGroupName_en-us": "metaGroupName"
+ 'invmetagroups': {
+ 'metaGroupName': 'metaGroupName'
},
- "evetypes": {
- "typeName_en-us": "typeName",
- "description_en-us": "description"
+ 'evetypes': {
+ 'typeName': 'typeName',
+ 'description': 'description'
},
#phbtraits???
- "mapbulk_marketGroups": {
- "marketGroupName_en-us": "marketGroupName",
- "description_en-us": "description"
+ 'mapbulk_marketGroups': {
+ 'marketGroupName': 'marketGroupName',
+ 'description': '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']
+ 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,8 +195,8 @@ 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['typeID'] in (41549, 41548, 41551, 41550) # Micro Bombs (Fighters)
@@ -204,14 +204,14 @@ def main(db, json_path):
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'].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,28 @@ 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%\'')
+
+ # fix for #1722 until CCP gets their shit together
+ eos.db.gamedata_engine.execute('UPDATE invtypes SET typeName = \'Small Abyssal Energy Nosferatu\' WHERE typeID = ? AND typeName = ?', (48419, ''))
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/fit.py b/service/fit.py
index 122de2fa6..d45219cad 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -187,11 +187,11 @@ class Fit(object):
# 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)
@@ -626,12 +626,11 @@ class Fit(object):
def changeModule(self, fitID, position, newItemID):
fit = eos.db.getFit(fitID)
+ module = fit.modules[position]
# We're trying to add a charge to a slot, which won't work. Instead, try to add the charge to the module in that slot.
- if self.isAmmo(newItemID):
- module = fit.modules[position]
- if not module.isEmpty:
- self.setAmmo(fitID, newItemID, [module])
+ if self.isAmmo(newItemID) and not module.isEmpty:
+ self.setAmmo(fitID, newItemID, [module])
return True
pyfalog.debug("Changing position of module from position ({0}) for fit ID: {1}", position, fitID)
@@ -640,14 +639,17 @@ class Fit(object):
# Dummy it out in case the next bit fails
fit.modules.toDummy(position)
-
+ ret = None
try:
m = es_Module(item)
except ValueError:
pyfalog.warning("Invalid item: {0}", newItemID)
return False
-
- if m.fits(fit):
+ if m.slot != module.slot:
+ fit.modules.toModule(position, module)
+ # Fits, but we selected wrong slot type, so don't want to overwrite because we will append on failure (none)
+ ret = None
+ elif m.fits(fit):
m.owner = fit
fit.modules.toModule(position, m)
if m.isValidState(State.ACTIVE):
@@ -661,9 +663,8 @@ class Fit(object):
fit.fill()
eos.db.commit()
- return True
- else:
- return None
+ ret = True
+ return ret
def moveCargoToModule(self, fitID, moduleIdx, cargoIdx, copyMod=False):
"""
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 e659a5899..5ad5c4ed3 100644
--- a/service/port.py
+++ b/service/port.py
@@ -24,7 +24,6 @@ from logbook import Logger
import collections
import json
import threading
-import locale
from bs4 import UnicodeDammit
@@ -33,6 +32,7 @@ from codecs import open
import xml.parsers.expat
from eos import db
+from eos.db.gamedata.queries import getAttributeInfo
from service.fit import Fit as svcFit
# noinspection PyPackageRequirements
@@ -1064,85 +1064,104 @@ class Port(object):
return fit_list
- @staticmethod
- def _exportEftBase(fit):
- """Basically EFT format does not require blank lines
- also, it's OK to arrange modules randomly?
- """
- offineSuffix = " /OFFLINE"
- export = "[%s, %s]\n" % (fit.ship.item.name, fit.name)
- stuff = {}
+
+ @classmethod
+ def exportEft(cls, fit, mutations=False, implants=False):
+ # EFT formatted export is split in several sections, each section is
+ # separated from another using 2 blank lines. Sections might have several
+ # sub-sections, which are separated by 1 blank line
+ sections = []
+
+ header = '[{}, {}]'.format(fit.ship.item.name, fit.name)
+
+ # Section 1: modules, rigs, subsystems, services
+ def formatAttrVal(val):
+ if int(val) == val:
+ return int(val)
+ return val
+
+ offineSuffix = ' /OFFLINE'
+ modsBySlotType = {}
sFit = svcFit.getInstance()
for module in fit.modules:
slot = module.slot
- if slot not in stuff:
- stuff[slot] = []
- curr = module.item.name if module.item \
- else ("[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "")
- if module.charge and sFit.serviceFittingOptions["exportCharges"]:
- curr += ", %s" % module.charge.name
- if module.state == State.OFFLINE:
- curr += offineSuffix
- curr += "\n"
- stuff[slot].append(curr)
-
+ slotTypeMods = modsBySlotType.setdefault(slot, [])
+ if module.item:
+ mutatedMod = bool(module.mutators)
+ # if module was mutated, use base item name for export
+ if mutatedMod:
+ modName = module.baseItem.name
+ else:
+ modName = module.item.name
+ modOfflineSuffix = offineSuffix if module.state == State.OFFLINE else ''
+ if module.charge and sFit.serviceFittingOptions['exportCharges']:
+ slotTypeMods.append('{}, {}{}'.format(modName, module.charge.name, modOfflineSuffix))
+ else:
+ slotTypeMods.append('{}{}'.format(modName, modOfflineSuffix))
+ if mutatedMod and mutations:
+ mutationGrade = module.mutaplasmid.item.name.split(' ', 1)[0].lower()
+ mutatedAttrs = {}
+ for attrID, mutator in module.mutators.items():
+ attrName = getAttributeInfo(attrID).name
+ mutatedAttrs[attrName] = mutator.value
+ customAttrsLine = ', '.join('{} {}'.format(a, formatAttrVal(mutatedAttrs[a])) for a in sorted(mutatedAttrs))
+ slotTypeMods.append(' {}: {}'.format(mutationGrade, customAttrsLine))
+ else:
+ slotTypeMods.append('[Empty {} slot]'.format(Slot.getName(slot).capitalize() if slot is not None else ''))
+ modSection = []
for slotType in EFT_SLOT_ORDER:
- data = stuff.get(slotType)
- if data is not None:
- export += "\n"
- for curr in data:
- export += curr
+ rackLines = []
+ data = modsBySlotType.get(slotType, ())
+ for line in data:
+ rackLines.append(line)
+ if rackLines:
+ modSection.append('\n'.join(rackLines))
+ if modSection:
+ sections.append('\n\n'.join(modSection))
- if len(fit.drones) > 0:
- export += "\n\n"
- for drone in fit.drones:
- export += "%s x%s\n" % (drone.item.name, drone.amount)
+ # Section 2: drones, fighters
+ minionSection = []
+ droneLines = []
+ for drone in sorted(fit.drones, key=lambda d: d.item.name):
+ droneLines.append('{} x{}'.format(drone.item.name, drone.amount))
+ if droneLines:
+ minionSection.append('\n'.join(droneLines))
+ fighterLines = []
+ for fighter in sorted(fit.fighters, key=lambda f: f.item.name):
+ fighterLines.append('{} x{}'.format(fighter.item.name, fighter.amountActive))
+ if fighterLines:
+ minionSection.append('\n'.join(fighterLines))
+ if minionSection:
+ sections.append('\n\n'.join(minionSection))
- if len(fit.fighters) > 0:
- export += "\n\n"
- for fighter in fit.fighters:
- export += "%s x%s\n" % (fighter.item.name, fighter.amountActive)
+ # Section 3: implants, boosters
+ if implants:
+ charSection = []
+ implantLines = []
+ for implant in fit.implants:
+ implantLines.append(implant.item.name)
+ if implantLines:
+ charSection.append('\n'.join(implantLines))
+ boosterLines = []
+ for booster in fit.boosters:
+ boosterLines.append(booster.item.name)
+ if boosterLines:
+ charSection.append('\n'.join(boosterLines))
+ if charSection:
+ sections.append('\n\n'.join(charSection))
- if export[-1] == "\n":
- export = export[:-1]
+ # Section 4: cargo
+ cargoLines = []
+ for cargo in sorted(fit.cargo, key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name)):
+ cargoLines.append('{} x{}'.format(cargo.item.name, cargo.amount))
+ if cargoLines:
+ sections.append('\n'.join(cargoLines))
- return export
-
- @classmethod
- def exportEft(cls, fit):
- export = cls._exportEftBase(fit)
-
- if len(fit.cargo) > 0:
- export += "\n\n\n"
- for cargo in fit.cargo:
- export += "%s x%s\n" % (cargo.item.name, cargo.amount)
- if export[-1] == "\n":
- export = export[:-1]
-
- return export
+ return '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
@classmethod
def exportEftImps(cls, fit):
- export = cls._exportEftBase(fit)
-
- if len(fit.implants) > 0 or len(fit.boosters) > 0:
- export += "\n\n\n"
- for implant in fit.implants:
- export += "%s\n" % implant.item.name
- for booster in fit.boosters:
- export += "%s\n" % booster.item.name
-
- if export[-1] == "\n":
- export = export[:-1]
-
- if len(fit.cargo) > 0:
- export += "\n\n\n"
- for cargo in fit.cargo:
- export += "%s x%s\n" % (cargo.item.name, cargo.amount)
- if export[-1] == "\n":
- export = export[:-1]
-
- return export
+ return cls.exportEft(fit, implants=True)
@staticmethod
def exportDna(fit):
diff --git a/service/price.py b/service/price.py
index 612937e39..2b8cd474b 100644
--- a/service/price.py
+++ b/service/price.py
@@ -228,10 +228,11 @@ class PriceWorkerThread(threading.Thread):
def trigger(self, prices, callbacks):
self.queue.put((callbacks, prices))
- def setToWait(self, itemID, callback):
- if itemID not in self.wait:
- self.wait[itemID] = []
- self.wait[itemID].append(callback)
+ def setToWait(self, prices, callback):
+ for x in prices:
+ if x.typeID not in self.wait:
+ self.wait[x.typeID] = []
+ self.wait[x.typeID].append(callback)
from service.marketSources import evemarketer, evemarketdata # noqa: E402
diff --git a/service/settings.py b/service/settings.py
index 75bdb5405..39f8def9c 100644
--- a/service/settings.py
+++ b/service/settings.py
@@ -248,6 +248,11 @@ class NetworkSettings(object):
proto = "{0}://".format(prefix)
if proxyline[:len(proto)] == proto:
proxyline = proxyline[len(proto):]
+ # sometimes proxyline contains "user:password@" section before proxy address
+ # remove it if present, so later split by ":" works
+ if '@' in proxyline:
+ userPass, proxyline = proxyline.split("@")
+ # TODO: do something with user/password?
proxAddr, proxPort = proxyline.split(":")
proxPort = int(proxPort.rstrip("/"))
proxy = (proxAddr, proxPort)