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)