diff --git a/.version b/.version index 524d0fb50..59d5e729e 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v1.33.2-104-g2365112 \ No newline at end of file +v1.2.3-221-g50dd74db \ No newline at end of file diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 621db8b52..765e3301d 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,11 +1,13 @@ -# Submit a bug report bug report or feature request + ## Bug Report diff --git a/README.md b/README.md index fd87aed48..2e09ca7f0 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,8 @@ The following is a list of pyfa packages available for certain distributions. Pl ### Dependencies If you wish to help with development or simply need to run pyfa through a Python interpreter, the following software is required: -* Python 2.7 -* `wxPython` 2.8/3.0 -* `sqlalchemy` >= 1.0.5 -* `dateutil` -* `matplotlib` (for some Linux distributions you may need to install separate wxPython bindings such as `python-matplotlib-wx`) -* `requests` -* `logbook` >= 1.0.0 +* Python 3.6 +* Requirements as listed in `requirements.txt` ## Bug Reporting The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting). diff --git a/dist_assets/mac/pyfa.spec b/dist_assets/mac/pyfa.spec index ece015d1c..55471fb2a 100644 --- a/dist_assets/mac/pyfa.spec +++ b/dist_assets/mac/pyfa.spec @@ -23,7 +23,7 @@ added_files = [ ('../../eve.db', '.'), ('../../README.md', '.'), ('../../LICENSE', '.'), - ('../../gitversion', '.'), + ('../../.version', '.'), ] diff --git a/eos/effects/energynosferatufalloff.py b/eos/effects/energynosferatufalloff.py index 841efb7cf..0dc4fd81b 100644 --- a/eos/effects/energynosferatufalloff.py +++ b/eos/effects/energynosferatufalloff.py @@ -12,7 +12,7 @@ def handler(fit, src, context, **kwargs): amount = src.getModifiedItemAttr("powerTransferAmount") time = src.getModifiedItemAttr("duration") - if 'effect' in kwargs: + if 'effect' in kwargs and "projected" in context: amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect']) if "projected" in context: diff --git a/eos/effects/fighterabilityecm.py b/eos/effects/fighterabilityecm.py index be4e41929..e821c748a 100644 --- a/eos/effects/fighterabilityecm.py +++ b/eos/effects/fighterabilityecm.py @@ -11,13 +11,14 @@ displayName = "ECM" prefix = "fighterAbilityECM" type = "projected", "active" +grouped = True def handler(fit, module, context, **kwargs): if "projected" not in context: return # jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str)) - strModifier = 1 - module.getModifiedItemAttr("{}Strength{}".format(prefix, fit.scanType)) / fit.scanStrength + strModifier = 1 - (module.getModifiedItemAttr("{}Strength{}".format(prefix, fit.scanType)) * module.amountActive) / fit.scanStrength if 'effect' in kwargs: strModifier *= ModifiedAttributeDict.getResistance(fit, kwargs['effect']) diff --git a/eos/effects/fighterabilityenergyneutralizer.py b/eos/effects/fighterabilityenergyneutralizer.py index 1e2734202..8a43dff19 100644 --- a/eos/effects/fighterabilityenergyneutralizer.py +++ b/eos/effects/fighterabilityenergyneutralizer.py @@ -9,11 +9,12 @@ from eos.modifiedAttributeDict import ModifiedAttributeDict displayName = "Energy Neutralizer" prefix = "fighterAbilityEnergyNeutralizer" type = "active", "projected" +grouped = True def handler(fit, src, context, **kwargs): if "projected" in context: - amount = src.getModifiedItemAttr("{}Amount".format(prefix)) + amount = src.getModifiedItemAttr("{}Amount".format(prefix)) * src.amountActive time = src.getModifiedItemAttr("{}Duration".format(prefix)) if 'effect' in kwargs: diff --git a/eos/effects/fighterabilitymicrowarpdrive.py b/eos/effects/fighterabilitymicrowarpdrive.py index 01bea209a..6dfbae73a 100644 --- a/eos/effects/fighterabilitymicrowarpdrive.py +++ b/eos/effects/fighterabilitymicrowarpdrive.py @@ -14,7 +14,8 @@ runTime = "late" def handler(fit, module, context): - module.boostItemAttr("maxVelocity", module.getModifiedItemAttr("fighterAbilityMicroWarpDriveSpeedBonus")) + module.boostItemAttr("maxVelocity", module.getModifiedItemAttr("fighterAbilityMicroWarpDriveSpeedBonus"), + stackingPenalties=True) module.boostItemAttr("signatureRadius", module.getModifiedItemAttr("fighterAbilityMicroWarpDriveSignatureRadiusBonus"), stackingPenalties=True) diff --git a/eos/effects/fighterabilitystasiswebifier.py b/eos/effects/fighterabilitystasiswebifier.py index cf5143f98..0aa3b5faf 100644 --- a/eos/effects/fighterabilitystasiswebifier.py +++ b/eos/effects/fighterabilitystasiswebifier.py @@ -6,13 +6,12 @@ effects, and thus this effect file contains some custom information useful only # User-friendly name for the ability displayName = "Stasis Webifier" - prefix = "fighterAbilityStasisWebifier" - type = "active", "projected" +grouped = True def handler(fit, src, context): if "projected" not in context: return - fit.ship.boostItemAttr("maxVelocity", src.getModifiedItemAttr("{}SpeedPenalty".format(prefix))) + fit.ship.boostItemAttr("maxVelocity", src.getModifiedItemAttr("{}SpeedPenalty".format(prefix)) * src.amountActive) diff --git a/eos/effects/fighterabilitywarpdisruption.py b/eos/effects/fighterabilitywarpdisruption.py index 0d7013434..d362fb33f 100644 --- a/eos/effects/fighterabilitywarpdisruption.py +++ b/eos/effects/fighterabilitywarpdisruption.py @@ -8,9 +8,10 @@ effects, and thus this effect file contains some custom information useful only displayName = "Warp Disruption" prefix = "fighterAbilityWarpDisruption" type = "active", "projected" +grouped = True def handler(fit, src, context): if "projected" not in context: return - fit.ship.increaseItemAttr("warpScrambleStatus", src.getModifiedItemAttr("{}PointStrength".format(prefix))) + fit.ship.increaseItemAttr("warpScrambleStatus", src.getModifiedItemAttr("{}PointStrength".format(prefix)) * src.amountActive) diff --git a/eos/effects/shipbonusrole2logisticdronerepamountandhitpointbonus.py b/eos/effects/shipbonusrole2logisticdronerepamountandhitpointbonus.py index e05e180ae..100056f61 100644 --- a/eos/effects/shipbonusrole2logisticdronerepamountandhitpointbonus.py +++ b/eos/effects/shipbonusrole2logisticdronerepamountandhitpointbonus.py @@ -6,15 +6,15 @@ type = "passive" def handler(fit, src, context): - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "structureDamageAmount", src.getModifiedItemAttr("shipBonusRole2")) - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "shieldBonus", src.getModifiedItemAttr("shipBonusRole2")) - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "armorDamageAmount", src.getModifiedItemAttr("shipBonusRole2")) - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "armorHP", src.getModifiedItemAttr("shipBonusRole2")) - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "shieldCapacity", src.getModifiedItemAttr("shipBonusRole2")) - fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Repair Drone Operation"), + fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Repair Drone Operation"), "hp", src.getModifiedItemAttr("shipBonusRole2")) diff --git a/eos/saveddata/fighter.py b/eos/saveddata/fighter.py index 645d7fbe5..a88189739 100644 --- a/eos/saveddata/fighter.py +++ b/eos/saveddata/fighter.py @@ -271,17 +271,19 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): projected = False for ability in self.abilities: - if ability.active: - effect = ability.effect - if effect.runTime == runTime and effect.activeByDefault and \ - ((projected and effect.isType("projected")) or not projected): - if ability.grouped: + if not ability.active: + continue + + effect = ability.effect + if effect.runTime == runTime and effect.activeByDefault and \ + ((projected and effect.isType("projected")) or not projected): + if ability.grouped: + effect.handler(fit, self, context) + else: + i = 0 + while i != self.amountActive: effect.handler(fit, self, context) - else: - i = 0 - while i != self.amountActive: - effect.handler(fit, self, context) - i += 1 + i += 1 def __deepcopy__(self, memo): copy = Fighter(self.item) diff --git a/gui/builtinShipBrowser/fitItem.py b/gui/builtinShipBrowser/fitItem.py index f76e3d6f9..fbc0a3020 100644 --- a/gui/builtinShipBrowser/fitItem.py +++ b/gui/builtinShipBrowser/fitItem.py @@ -362,12 +362,13 @@ class FitItem(SFItem.SFBrowserItem): self.deleted = True sFit = Fit.getInstance() - fit = sFit.getFit(self.fitID) # need to delete from import cache before actually deleting fit if self.shipBrowser.GetActiveStage() == 5: - if fit in self.shipBrowser.lastdata: # remove fit from import cache - self.shipBrowser.lastdata.remove(fit) + for x in self.shipBrowser.lastdata: # remove fit from import cache + if x[0] == self.fitID: + self.shipBrowser.lastdata.remove(x) + break sFit.deleteFit(self.fitID) @@ -376,7 +377,7 @@ class FitItem(SFItem.SFBrowserItem): # todo: would a simple RefreshList() work here instead of posting that a stage has been selected? if self.shipBrowser.GetActiveStage() == 5: - wx.PostEvent(self.shipBrowser, ImportSelected(fits=self.shipBrowser.lastdata)) + wx.PostEvent(self.shipBrowser, ImportSelected(fits=self.shipBrowser.lastdata, recent=self.shipBrowser.recentFits)) elif self.shipBrowser.GetActiveStage() == 4: wx.PostEvent(self.shipBrowser, SearchSelected(text=self.shipBrowser.navpanel.lastSearch, back=True)) else: diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 7e6eb9db2..a7c1ea7b7 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -242,8 +242,8 @@ class MainFrame(wx.Frame): self.titleTimer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.updateTitle, self.titleTimer) - def ShowUpdateBox(self, release): - dlg = UpdateDialog(self, release) + def ShowUpdateBox(self, release, version): + dlg = UpdateDialog(self, release, version) dlg.ShowModal() def LoadPreviousOpenFits(self): diff --git a/gui/updateDialog.py b/gui/updateDialog.py index 83b8426b3..a38de401f 100644 --- a/gui/updateDialog.py +++ b/gui/updateDialog.py @@ -22,12 +22,33 @@ import wx # noinspection PyPackageRequirements import dateutil.parser from service.settings import UpdateSettings as svc_UpdateSettings +import wx.html2 +import webbrowser +import re +import markdown2 + +# HTML template. We link to a bootstrap cdn for quick and easy css, and include some additional teaks. +html_tmpl = """ + + +

pyfa {0}

+
{1}
+
+{2} +{3} +""" class UpdateDialog(wx.Dialog): - def __init__(self, parent, release): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Pyfa Update", pos=wx.DefaultPosition, - size=wx.Size(400, 300), style=wx.DEFAULT_DIALOG_STYLE) + def __init__(self, parent, release, version): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="pyfa Update Available", pos=wx.DefaultPosition, + size=wx.Size(550, 450), style=wx.DEFAULT_DIALOG_STYLE) self.UpdateSettings = svc_UpdateSettings.getInstance() self.releaseInfo = release @@ -35,51 +56,28 @@ class UpdateDialog(wx.Dialog): mainSizer = wx.BoxSizer(wx.VERTICAL) - headSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.headingText = wx.StaticText(self, wx.ID_ANY, "Pyfa Update Available!", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_CENTRE) - self.headingText.Wrap(-1) - self.headingText.SetFont(wx.Font(14, 74, 90, 92, False)) - - headSizer.Add(self.headingText, 1, wx.ALL, 5) - mainSizer.Add(headSizer, 0, wx.EXPAND, 5) - - mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, - wx.EXPAND | wx.ALL, 5) - - versionSizer = wx.BoxSizer(wx.HORIZONTAL) - - if self.releaseInfo['prerelease']: - self.releaseText = wx.StaticText(self, wx.ID_ANY, "Pre-release", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_RIGHT) - self.releaseText.SetFont(wx.Font(12, 74, 90, 92, False)) - self.releaseText.SetForegroundColour(wx.Colour(230, 0, 0)) - else: - self.releaseText = wx.StaticText(self, wx.ID_ANY, "Stable", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_RIGHT) - self.releaseText.SetFont(wx.Font(12, 74, 90, 90, False)) - - self.releaseText.Wrap(-1) - - versionSizer.Add(self.releaseText, 1, wx.ALL, 5) - - self.versionText = wx.StaticText(self, wx.ID_ANY, self.releaseInfo['tag_name'], wx.DefaultPosition, - wx.DefaultSize, wx.ALIGN_LEFT) - self.versionText.Wrap(-1) - self.versionText.SetFont(wx.Font(12, 74, 90, 90, False)) - - versionSizer.Add(self.versionText, 1, wx.ALL, 5) - - mainSizer.Add(versionSizer, 0, wx.EXPAND, 0) - releaseDate = dateutil.parser.parse(self.releaseInfo['published_at']) notesSizer = wx.BoxSizer(wx.HORIZONTAL) - self.notesTextCtrl = wx.TextCtrl(self, wx.ID_ANY, str(releaseDate.date()) + ":\n\n" + self.releaseInfo['body'], - wx.DefaultPosition, wx.DefaultSize, - wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_READONLY | wx.DOUBLE_BORDER | wx.TRANSPARENT_WINDOW) + self.browser = wx.html2.WebView.New(self) + self.browser.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.OnNewWindow) - notesSizer.Add(self.notesTextCtrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) + link_patterns = [ + (re.compile("#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"), + (re.compile("@(\w+)", re.I), r"https://github.com/\1") + ] + + markdowner = markdown2.Markdown( + extras=['cuddled-lists', 'fenced-code-blocks', 'target-blank-links', 'toc', 'link-patterns'], + link_patterns=link_patterns) + + self.browser.SetPage(html_tmpl.format( + self.releaseInfo['tag_name'], + releaseDate.strftime('%B %d, %Y'), + "

This is a pre-release, be prepared for unstable features

" if version.is_prerelease else "", + markdowner.convert(self.releaseInfo['body']) + ),"") + + notesSizer.Add(self.browser, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) mainSizer.Add(notesSizer, 1, wx.EXPAND, 5) self.supressCheckbox = wx.CheckBox(self, wx.ID_ANY, "Don't remind me again for this release", @@ -117,6 +115,10 @@ class UpdateDialog(wx.Dialog): def OnClose(self, e): self.Close() + def OnNewWindow(self, event): + url = event.GetURL() + webbrowser.open(url) + def SuppressChange(self, e): if self.supressCheckbox.IsChecked(): self.UpdateSettings.set('version', self.releaseInfo['tag_name']) diff --git a/requirements.txt b/requirements.txt index 8216804cf..7462f9f5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ -wxPython >= 4.0.0b2 +wxPython >= 4.0.1 logbook >= 1.0.0 matplotlib >= 2.0.0 python-dateutil -urllib3 -requests == 2.0.0 -sqlalchemy == 1.0.5 -esipy == 0.3.0 \ No newline at end of file +requests >= 2.0.0 +sqlalchemy >= 1.0.5 +esipy == 0.3.0 +markdown2 +packaging \ No newline at end of file diff --git a/service/port.py b/service/port.py index 8f2851bf6..eec71ab73 100644 --- a/service/port.py +++ b/service/port.py @@ -54,13 +54,14 @@ from collections import OrderedDict pyfalog = Logger(__name__) -EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM] +EFT_SLOT_ORDER = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE] INV_FLAGS = { Slot.LOW: 11, Slot.MED: 19, Slot.HIGH: 27, Slot.RIG: 92, - Slot.SUBSYSTEM: 125 + Slot.SUBSYSTEM: 125, + Slot.SERVICE: 164 } INV_FLAG_CARGOBAY = 5 diff --git a/service/update.py b/service/update.py index 7196b2a59..e4cdbe03f 100644 --- a/service/update.py +++ b/service/update.py @@ -30,6 +30,8 @@ import config from service.network import Network from service.settings import UpdateSettings from logbook import Logger +from packaging.version import Version + pyfalog = Logger(__name__) @@ -46,7 +48,7 @@ class CheckUpdateThread(threading.Thread): network = Network.getInstance() try: - response = network.request('https://api.github.com/repos/pyfa-org/Pyfa/releases', network.UPDATE) + response = network.request('https://api.github.com/repos/blitzmann/Pyfa/releases', network.UPDATE) jsonResponse = json.loads(response.read()) jsonResponse.sort( key=lambda x: calendar.timegm(dateutil.parser.parse(x['published_at']).utctimetuple()), @@ -54,8 +56,11 @@ class CheckUpdateThread(threading.Thread): ) for release in jsonResponse: + rVersion = Version(release['tag_name']) + cVersion = Version(config.version) + # Suppress pre releases - if release['prerelease'] and self.settings.get('prerelease'): + if rVersion.is_prerelease and self.settings.get('prerelease'): continue # Handle use-case of updating to suppressed version @@ -66,24 +71,9 @@ class CheckUpdateThread(threading.Thread): if release['tag_name'] == self.settings.get('version'): break - # Set the release version that we will be comparing with. - if release['prerelease']: - rVersion = release['tag_name'].replace('singularity-', '', 1) - else: - rVersion = release['tag_name'].replace('v', '', 1) + if rVersion > cVersion: + wx.CallAfter(self.callback, release, rVersion) - if config.tag is 'git' and \ - not release['prerelease'] and \ - self.versiontuple(rVersion) >= self.versiontuple(config.version): - wx.CallAfter(self.callback, release) # git (dev/Singularity) -> Stable - elif config.expansionName is not "Singularity": - if release['prerelease']: - wx.CallAfter(self.callback, release) # Stable -> Singularity - elif self.versiontuple(rVersion) > self.versiontuple(config.version): - wx.CallAfter(self.callback, release) # Stable -> Stable - else: - if release['prerelease'] and rVersion > config.expansionVersion: - wx.CallAfter(self.callback, release) # Singularity -> Singularity break except Exception as e: pyfalog.error("Caught exception in run")