diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py index b07484848..bd5812c4b 100755 --- a/eos/saveddata/drone.py +++ b/eos/saveddata/drone.py @@ -23,6 +23,7 @@ from sqlalchemy.orm import validates, reconstructor class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage") + MINING_ATTRIBUTES = ("miningAmount",) def __init__(self, item): if item.category.name != "Drone": @@ -34,6 +35,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.amount = 0 self.amountActive = 0 self.__dps = None + self.__miningyield = None self.projected = False self.__itemModifiedAttributes = ModifiedAttributeDict() self.itemModifiedAttributes.original = self.item.attributes @@ -41,6 +43,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @reconstructor def init(self): self.__dps = None + self.__miningyield = None self.__item = None self.__charge = None @@ -97,6 +100,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if attr in self.itemModifiedAttributes or attr in self.chargeModifiedAttributes: return True + @property + def mines(self): + if "miningAmount" in self.itemModifiedAttributes: + return True + @property def hasAmmo(self): return self.charge is not None @@ -121,6 +129,21 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): return self.__dps + @property + def miningStats(self): + if self.__miningyield == None: + if self.mines is True and self.amountActive > 0: + attr = "duration" + getter = self.getModifiedItemAttr + + cycleTime = self.getModifiedItemAttr(attr) + volley = sum(map(lambda d: getter(d), self.MINING_ATTRIBUTES)) * self.amountActive + self.__miningyield = volley / (cycleTime / 1000.0) + else: + self.__miningyield = 0 + + return self.__miningyield + @property def maxRange(self): attrs = ("shieldTransferRange", "powerTransferRange", @@ -158,6 +181,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def clear(self): self.__dps = None + self.__miningyield = None self.itemModifiedAttributes.clear() self.chargeModifiedAttributes.clear() diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 1c56b303a..acafbcfb7 100755 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -76,8 +76,10 @@ class Fit(object): self.__extraDrains = [] self.__ehp = None self.__weaponDPS = None + self.__minerYield = None self.__weaponVolley = None self.__droneDPS = None + self.__droneYield = None self.__sustainableTank = None self.__effectiveSustainableTank = None self.__effectiveTank = None @@ -179,6 +181,24 @@ class Fit(object): def totalDPS(self): return self.droneDPS + self.weaponDPS + @property + def minerYield(self): + if self.__minerYield is None: + self.calculateMiningStats() + + return self.__minerYield + + @property + def droneYield(self): + if self.__droneYield is None: + self.calculateMiningStats() + + return self.__droneYield + + @property + def totalYield(self): + return self.droneYield + self.minerYield + @property def maxTargets(self): return min(self.extraAttributes["maxTargetsLockedFromSkills"], self.ship.getModifiedItemAttr("maxLockedTargets")) @@ -237,10 +257,12 @@ class Fit(object): def clear(self): self.__effectiveTank = None self.__weaponDPS = None + self.__minerYield = None self.__weaponVolley = None self.__effectiveSustainableTank = None self.__sustainableTank = None self.__droneDPS = None + self.__droneYield = None self.__ehp = None self.__calculated = False self.__capStable = None @@ -763,6 +785,19 @@ class Fit(object): else: return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0 + def calculateMiningStats(self): + minerYield = 0 + droneYield = 0 + + for mod in self.modules: + minerYield += mod.miningStats + + for drone in self.drones: + droneYield += drone.miningStats + + self.__minerYield = minerYield + self.__droneYield = droneYield + def calculateWeaponStats(self): weaponDPS = 0 droneDPS = 0 diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index ec7bf7874..2e68816c3 100755 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -45,6 +45,7 @@ class Hardpoint(Enum): class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): """An instance of this class represents a module together with its charge and modified attributes""" DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage") + MINING_ATTRIBUTES = ("miningAmount", ) def __init__(self, item): self.__item = item if item != None else 0 @@ -53,6 +54,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.projected = False self.state = State.ONLINE self.__dps = None + self.__miningyield = None self.__volley = None self.__reloadTime = None self.__reloadForce = None @@ -74,6 +76,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__charge = None self.__volley = None self.__dps = None + self.__miningyield = None self.__reloadTime = None self.__reloadForce = None self.__chargeCycles = None @@ -82,6 +85,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__item = 0 self.__charge = 0 self.__dps = 0 + self.__miningyield = 0 self.__volley = 0 self.__reloadTime = 0 self.__reloadForce = None @@ -330,6 +334,24 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): return self.__dps, self.__volley + @property + def miningStats(self): + if self.__miningyield == None: + if self.isEmpty: + self.__miningyield = 0 + else: + if self.state >= State.ACTIVE: + volley = sum(map(lambda attr: self.getModifiedItemAttr(attr) or 0, self.MINING_ATTRIBUTES)) + if volley: + cycleTime = self.cycleTime + self.__miningyield = volley / (cycleTime / 1000.0) + else: + self.__miningyield = 0 + else: + self.__miningyield = 0 + + return self.__miningyield + @property def dps(self): return self.damageStats[0] @@ -552,6 +574,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def clear(self): self.__dps = None + self.__miningyield = None self.__volley = None self.__reloadTime = None self.__reloadForce = None diff --git a/gui/builtinStatsViews/__init__.py b/gui/builtinStatsViews/__init__.py index e4e7b9369..5f2ca646e 100644 --- a/gui/builtinStatsViews/__init__.py +++ b/gui/builtinStatsViews/__init__.py @@ -1,3 +1,3 @@ __all__ = ["resourcesViewFull", "resistancesViewFull", "rechargeViewFull", "firepowerViewFull", "capacitorViewFull", - "targetingMiscViewFull", "priceViewFull"] + "targetingMiscViewFull", "priceViewFull", "miningyieldViewFull"] diff --git a/gui/builtinStatsViews/firepowerViewFull.py b/gui/builtinStatsViews/firepowerViewFull.py index 7db56074d..1b26b2662 100644 --- a/gui/builtinStatsViews/firepowerViewFull.py +++ b/gui/builtinStatsViews/firepowerViewFull.py @@ -18,8 +18,9 @@ #=============================================================================== import wx +import service +import gui.mainFrame from gui.statsView import StatsView -from gui import builtinStatsViews from gui import bitmapLoader from gui.utils.numberFormatter import formatAmount @@ -43,9 +44,9 @@ class FirepowerViewFull(StatsView): panel = "full" - sizerFirepower = wx.FlexGridSizer(1, 3) + sizerFirepower = wx.FlexGridSizer(1, 4) sizerFirepower.AddGrowableCol(1) - + contentSizer.Add( sizerFirepower, 0, wx.EXPAND, 0) counter = 0 @@ -97,6 +98,35 @@ class FirepowerViewFull(StatsView): gridS.Add(lbl, 0, wx.ALIGN_LEFT) + image = bitmapLoader.getBitmap("mining_small", "icons") + self.miningyield = wx.BitmapButton(contentPanel, -1, image) + self.miningyield.SetToolTip(wx.ToolTip("Click to toggle to Mining Yield ")) + self.miningyield.Bind(wx.EVT_BUTTON, self.switchToMiningYieldView) + sizerFirepower.Add(self.miningyield, 0, wx.ALIGN_LEFT) + + self._cachedValues.append(0) + + def switchToMiningYieldView(self, event): + # Getting the active fit + mainFrame = gui.mainFrame.MainFrame.getInstance() + sFit = service.Fit.getInstance() + fit = sFit.getFit(mainFrame.getActiveFit()) + # Remove ourselves from statsPane's view list + self.parent.views.remove(self) + self._cachedValues = [] + # And no longer display us + self.panel.GetSizer().Clear(True) + self.panel.GetSizer().Layout() + # Get the new view + view = StatsView.getView("miningyieldViewFull")(self.parent) + view.populatePanel(self.panel, self.headerPanel) + # Populate us in statsPane's view list + self.parent.views.append(view) + # Get the TogglePanel + tp = self.panel.GetParent() + tp.SetLabel(view.getHeaderText(fit)) + view.refreshPanel(fit) + def refreshPanel(self, fit): #If we did anything intresting, we'd update our labels to reflect the new fit's stats here @@ -104,6 +134,11 @@ class FirepowerViewFull(StatsView): ("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None), ("labelFullVolleyTotal", lambda: fit.weaponVolley, 3, 0, 0, "%s", "Volley: %.1f"), ("labelFullDpsTotal", lambda: fit.totalDPS, 3, 0, 0, "%s", None)) + # See GH issue # + #if fit is not None and fit.totalYield > 0: + # self.miningyield.Show() + #else: + # self.miningyield.Hide() counter = 0 for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats: @@ -117,6 +152,7 @@ class FirepowerViewFull(StatsView): label.SetToolTip(wx.ToolTip(tipStr)) self._cachedValues[counter] = value counter +=1 + self.panel.Layout() self.headerPanel.Layout() diff --git a/gui/builtinStatsViews/miningyieldViewFull.py b/gui/builtinStatsViews/miningyieldViewFull.py new file mode 100644 index 000000000..a83800d70 --- /dev/null +++ b/gui/builtinStatsViews/miningyieldViewFull.py @@ -0,0 +1,146 @@ +#=============================================================================== +# Copyright (C) 2014 Alexandros Kosiaris +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +#=============================================================================== + +import wx +import service +import gui.mainFrame +from gui.statsView import StatsView +from gui import bitmapLoader +from gui.utils.numberFormatter import formatAmount + +class MiningYieldViewFull(StatsView): + name = "miningyieldViewFull" + def __init__(self, parent): + StatsView.__init__(self) + self.parent = parent + self._cachedValues = [] + def getHeaderText(self, fit): + return "Mining Yield" + + def getTextExtentW(self, text): + width, height = self.parent.GetTextExtent( text ) + return width + + def populatePanel(self, contentPanel, headerPanel): + contentSizer = contentPanel.GetSizer() + parent = self.panel = contentPanel + self.headerPanel = headerPanel + + panel = "full" + + sizerMiningYield = wx.FlexGridSizer(1, 4) + sizerMiningYield.AddGrowableCol(1) + + contentSizer.Add( sizerMiningYield, 0, wx.EXPAND, 0) + + counter = 0 + + for miningType, image in (("miner", "mining") , ("drone", "drones")): + baseBox = wx.BoxSizer(wx.HORIZONTAL) + sizerMiningYield.Add(baseBox, 1, wx.ALIGN_LEFT if counter == 0 else wx.ALIGN_CENTER_HORIZONTAL) + + baseBox.Add(bitmapLoader.getStaticBitmap("%s_big" % image, parent, "icons"), 0, wx.ALIGN_CENTER) + + box = wx.BoxSizer(wx.VERTICAL) + baseBox.Add(box, 0, wx.ALIGN_CENTER) + + box.Add(wx.StaticText(parent, wx.ID_ANY, miningType.capitalize()), 0, wx.ALIGN_LEFT) + + hbox = wx.BoxSizer(wx.HORIZONTAL) + box.Add(hbox, 1, wx.ALIGN_CENTER) + + lbl = wx.StaticText(parent, wx.ID_ANY, u"0.0 m\u00B3/s") + setattr(self, "label%sminingyield%s" % (panel.capitalize() ,miningType.capitalize()), lbl) + + hbox.Add(lbl, 0, wx.ALIGN_CENTER) + self._cachedValues.append(0) + counter += 1 + targetSizer = sizerMiningYield + + baseBox = wx.BoxSizer(wx.HORIZONTAL) + targetSizer.Add(baseBox, 0, wx.ALIGN_LEFT) + + baseBox.Add(bitmapLoader.getStaticBitmap("cargoBay_big", parent, "icons"), 0, wx.ALIGN_CENTER) + + box = wx.BoxSizer(wx.VERTICAL) + baseBox.Add(box, 0, wx.EXPAND) + + box.Add(wx.StaticText(parent, wx.ID_ANY, "Total"), 0, wx.ALIGN_LEFT) + + hbox = wx.BoxSizer(wx.HORIZONTAL) + box.Add(hbox, 1, wx.EXPAND) + + lbl = wx.StaticText(parent, wx.ID_ANY, u"0.0 m\u00B3/s") + setattr(self, "label%sminingyieldTotal" % panel.capitalize(), lbl) + hbox.Add(lbl, 0, wx.ALIGN_LEFT) + + self._cachedValues.append(0) + + image = bitmapLoader.getBitmap("turret_small", "icons") + firepower = wx.BitmapButton(contentPanel, -1, image) + firepower.SetToolTip(wx.ToolTip("Click to toggle to Firepower View")) + firepower.Bind(wx.EVT_BUTTON, self.switchToFirepowerView) + sizerMiningYield.Add(firepower, 0, wx.ALIGN_LEFT) + + self._cachedValues.append(0) + + def switchToFirepowerView(self, event): + # Getting the active fit + mainFrame = gui.mainFrame.MainFrame.getInstance() + sFit = service.Fit.getInstance() + fit = sFit.getFit(mainFrame.getActiveFit()) + # Remove ourselves from statsPane's view list + self.parent.views.remove(self) + self._cachedValues = [] + # And no longer display us + self.panel.GetSizer().Clear(True) + self.panel.GetSizer().Layout() + # Get the new view + view = StatsView.getView("firepowerViewFull")(self.parent) + view.populatePanel(self.panel, self.headerPanel) + # Populate us in statsPane's view list + self.parent.views.append(view) + # Get the TogglePanel + tp = self.panel.GetParent() + tp.SetLabel(view.getHeaderText(fit)) + view.refreshPanel(fit) + + def refreshPanel(self, fit): + #If we did anything intresting, we'd update our labels to reflect the new fit's stats here + + stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, 3, 0, 0, u"%s m\u00B3/s",None), + ("labelFullminingyieldDrone", lambda: fit.droneYield, 3, 0, 0, u"%s m\u00B3/s", None), + ("labelFullminingyieldTotal", lambda: fit.totalYield, 3, 0, 0, u"%s m\u00B3/s", None)) + + counter = 0 + for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats: + label = getattr(self, labelName) + value = value() if fit is not None else 0 + value = value if value is not None else 0 + if self._cachedValues[counter] != value: + valueStr = formatAmount(value, prec, lowest, highest) + label.SetLabel(valueFormat % valueStr) + tipStr = valueFormat % valueStr if altFormat is None else altFormat % value + label.SetToolTip(wx.ToolTip(tipStr)) + self._cachedValues[counter] = value + counter +=1 + self.panel.Layout() + self.headerPanel.Layout() + +MiningYieldViewFull.register() diff --git a/gui/statsPane.py b/gui/statsPane.py index 37331e875..7bac879fd 100644 --- a/gui/statsPane.py +++ b/gui/statsPane.py @@ -29,7 +29,8 @@ import gui.globalEvents as GE class StatsPane(wx.Panel): DEFAULT_VIEWS = ["resourcesViewFull", "resistancesViewFull" ,"rechargeViewFull", "firepowerViewFull", - "capacitorViewFull", "targetingmiscViewFull", "priceViewFull"] + "capacitorViewFull", "targetingmiscViewFull", + "priceViewFull",] def fitChanged(self, event): cFit = service.Fit.getInstance() diff --git a/icons/mining_small.png b/icons/mining_small.png new file mode 100644 index 000000000..78357a000 Binary files /dev/null and b/icons/mining_small.png differ diff --git a/icons/turret_small.png b/icons/turret_small.png new file mode 100644 index 000000000..e9047086b Binary files /dev/null and b/icons/turret_small.png differ