From 9ef182aa99f67e6086aa8425051a1a9f2e22cf74 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 6 Jul 2015 23:39:20 -0400 Subject: [PATCH 01/34] First working prototype of toggleable projected fits. Creates a new association object that stores projection-specific information. GUI hasn't been touched (need to show state), and there are a lot of variables that I need to rename. --- eos/db/saveddata/fit.py | 123 +++++++++++++++++++++++++++--------- eos/effectHandlerHelpers.py | 2 + eos/saveddata/fit.py | 3 +- service/fit.py | 3 + 4 files changed, 100 insertions(+), 31 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 0c5edaee9..3b5fc0e3e 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -17,9 +17,10 @@ # along with eos. If not, see . #=============================================================================== -from sqlalchemy import Table, Column, Integer, ForeignKey, String, Boolean -from sqlalchemy.orm import relation, mapper +from sqlalchemy import * +from sqlalchemy.orm import * from sqlalchemy.sql import and_ +from sqlalchemy.ext.associationproxy import association_proxy from eos.db import saveddata_meta from eos.db.saveddata.module import modules_table @@ -46,32 +47,94 @@ projectedFits_table = Table("projectedFits", saveddata_meta, Column("sourceID", ForeignKey("fits.ID"), primary_key = True), Column("victimID", ForeignKey("fits.ID"), primary_key = True), Column("amount", Integer)) + +class ProjectedFit(object): + def __init__(self, source_item, dest_item, enabled): + print "init projected item", source_item, dest_item, enabled + self.source_item = source_item + self.dest_item = dest_item + self.amount = enabled + + @reconstructor + def init(self): + print "db init" + print "\t source:",self.source_item + print "\t dest:", self.dest_item + self.dest_item.projectionInfo = self + +Fit._Fit__projectedFits = association_proxy( + "projected_items", + "dest_item", + creator=lambda dest_item: ProjectedFit(None, dest_item, True) +) + mapper(Fit, fits_table, - properties = {"_Fit__modules" : relation(Module, collection_class = HandledModuleList, - primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False), - order_by = modules_table.c.position, cascade='all, delete, delete-orphan'), - "_Fit__projectedModules" : relation(Module, collection_class = HandledProjectedModList, cascade='all, delete, delete-orphan', single_parent=True, - primaryjoin = and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)), - "owner" : relation(User, backref = "fits"), - "itemID" : fits_table.c.shipID, - "shipID" : fits_table.c.shipID, - "_Fit__boosters" : relation(Booster, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', single_parent=True), - "_Fit__drones" : relation(Drone, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True, - primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)), - "_Fit__cargo" : relation(Cargo, collection_class = HandledDroneCargoList, cascade='all, delete, delete-orphan', single_parent=True, - primaryjoin = and_(cargo_table.c.fitID == fits_table.c.ID)), - "_Fit__projectedDrones" : relation(Drone, collection_class = HandledProjectedDroneList, cascade='all, delete, delete-orphan', single_parent=True, - primaryjoin = and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)), - "_Fit__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all, delete, delete-orphan', backref='fit', single_parent=True, - primaryjoin = fitImplants_table.c.fitID == fits_table.c.ID, - secondaryjoin = fitImplants_table.c.implantID == Implant.ID, - secondary = fitImplants_table), - "_Fit__character" : relation(Character, backref = "fits"), - "_Fit__damagePattern" : relation(DamagePattern), - "_Fit__targetResists" : relation(TargetResists), - "_Fit__projectedFits" : relation(Fit, - primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID, - secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID, - secondary = projectedFits_table, - collection_class = HandledProjectedFitList) - }) + properties = { + "_Fit__modules": relation( + Module, + collection_class=HandledModuleList, + primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == False), + order_by=modules_table.c.position, + cascade='all, delete, delete-orphan'), + "_Fit__projectedModules": relation( + Module, + collection_class=HandledProjectedModList, + cascade='all, delete, delete-orphan', + single_parent=True, + primaryjoin=and_(modules_table.c.fitID == fits_table.c.ID, modules_table.c.projected == True)), + "owner": relation( + User, + backref="fits"), + "itemID": fits_table.c.shipID, + "shipID": fits_table.c.shipID, + "_Fit__boosters": relation( + Booster, + collection_class=HandledImplantBoosterList, + cascade='all, delete, delete-orphan', + single_parent=True), + "_Fit__drones": relation( + Drone, + collection_class=HandledDroneCargoList, + cascade='all, delete, delete-orphan', + single_parent=True, + primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == False)), + "_Fit__cargo": relation( + Cargo, + collection_class=HandledDroneCargoList, + cascade='all, delete, delete-orphan', + single_parent=True, + primaryjoin=and_(cargo_table.c.fitID == fits_table.c.ID)), + "_Fit__projectedDrones": relation( + Drone, + collection_class=HandledProjectedDroneList, + cascade='all, delete, delete-orphan', + single_parent=True, + primaryjoin=and_(drones_table.c.fitID == fits_table.c.ID, drones_table.c.projected == True)), + "_Fit__implants": relation( + Implant, + collection_class=HandledImplantBoosterList, + cascade='all, delete, delete-orphan', + backref='fit', + single_parent=True, + primaryjoin=fitImplants_table.c.fitID == fits_table.c.ID, + secondaryjoin=fitImplants_table.c.implantID == Implant.ID, + secondary=fitImplants_table), + "_Fit__character": relation( + Character, + backref="fits"), + "_Fit__damagePattern": relation(DamagePattern), + "_Fit__targetResists": relation(TargetResists), + "dest_items": relationship( + ProjectedFit, + primaryjoin=projectedFits_table.c.victimID == fits_table.c.ID, + backref='dest_item', + cascade='all, delete, delete-orphan'), + "projected_items": relationship( + ProjectedFit, + primaryjoin=fits_table.c.ID == projectedFits_table.c.sourceID, + backref='source_item', + cascade='all, delete, delete-orphan'), + } +) + +mapper(ProjectedFit, projectedFits_table) diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index c658cf0a9..fee65fad0 100644 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -228,8 +228,10 @@ class HandledProjectedDroneList(HandledDroneCargoList): if proj.isInvalid or not proj.item.isType("projected"): self.remove(proj) +# @todo: remove this once we are sure we no longer need it class HandledProjectedFitList(HandledList): def append(self, proj): + print "apppending projected fit", proj proj.projected = True list.append(self, proj) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 5c03d6a2a..12d9b4cd8 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -452,7 +452,8 @@ class Fit(object): item.calculateModifiedAttributes(targetFit, runTime, True) for fit in self.projectedFits: - fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) + if fit.projectionInfo.amount: + fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) def fill(self): """ diff --git a/service/fit.py b/service/fit.py index f20d9183a..9ce47ad62 100644 --- a/service/fit.py +++ b/service/fit.py @@ -363,6 +363,9 @@ class Fit(object): thing.state = self.__getProposedState(thing, click) if not thing.canHaveState(thing.state, fit): thing.state = State.OFFLINE + elif isinstance(thing, eos.types.Fit): + print "toggle fit" + thing.projectionInfo.amount = not thing.projectionInfo.amount eos.db.commit() self.recalc(fit) From 2bca3ddcc87b04e833e2c390c4ea005f05d375d6 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 7 Jul 2015 00:12:36 -0400 Subject: [PATCH 02/34] GUI support (also made regular checkboxes pretty for drones/implant/etc) --- eos/saveddata/fit.py | 11 ++++++++++- gui/builtinViewColumns/state.py | 21 +++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 12d9b4cd8..b0b94c2df 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -117,6 +117,7 @@ class Fit(object): self.__capUsed = None self.__capRecharge = None self.__calculatedTargets = [] + self.__projectionInfo = None self.factorReload = False self.fleet = None self.boostsFits = set() @@ -207,6 +208,14 @@ class Fit(object): def projectedFits(self): return self.__projectedFits + @property + def projectionInfo(self): + return self.__projectionInfo + + @projectionInfo.setter + def projectionInfo(self, projectionInfo): + self.__projectionInfo = projectionInfo + @property def projectedDrones(self): return self.__projectedDrones @@ -452,7 +461,7 @@ class Fit(object): item.calculateModifiedAttributes(targetFit, runTime, True) for fit in self.projectedFits: - if fit.projectionInfo.amount: + if fit.projectionInfo.amount > 0: fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) def fill(self): diff --git a/gui/builtinViewColumns/state.py b/gui/builtinViewColumns/state.py index d26c46443..c10a0f77f 100644 --- a/gui/builtinViewColumns/state.py +++ b/gui/builtinViewColumns/state.py @@ -20,7 +20,7 @@ from gui.viewColumn import ViewColumn from gui import bitmapLoader import wx -from eos.types import Drone, Module, Rack +from eos.types import Drone, Module, Rack, Fit from eos.types import State as State_ class State(ViewColumn): @@ -49,8 +49,14 @@ class State(ViewColumn): return State_.getName(mod.state).title() def getImageId(self, stuff): + generic_active = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(1).lower(), "icons") + generic_inactive = self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(-1).lower(), "icons") + if isinstance(stuff, Drone): - return self.checkedId if stuff.amountActive > 0 else self.uncheckedId + if stuff.amountActive > 0: + return generic_active + else: + return generic_inactive elif isinstance(stuff, Rack): return -1 elif isinstance(stuff, Module): @@ -58,11 +64,18 @@ class State(ViewColumn): return -1 else: return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons") + elif isinstance(stuff, Fit): + if stuff.projectionInfo is None: + return -1 + if stuff.projectionInfo.amount > 0: + return generic_active + return generic_inactive else: active = getattr(stuff, "active", None) if active is None: return -1 - else: - return self.checkedId if active else self.uncheckedId + if active: + return generic_active + return generic_inactive State.register() From b95a10d284a8b0dad41c72486b356f484d897fd4 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 7 Jul 2015 00:25:24 -0400 Subject: [PATCH 03/34] Add active column. Looping the fit to apply it x amount of times doesn't seem to work. Probably because it's been flagged calculated and returns early --- eos/db/saveddata/fit.py | 14 +++++++++----- eos/saveddata/fit.py | 5 +++-- gui/builtinViewColumns/baseName.py | 2 +- gui/builtinViewColumns/state.py | 2 +- service/fit.py | 2 +- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 3b5fc0e3e..4c03bdd32 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -46,14 +46,18 @@ fits_table = Table("fits", saveddata_meta, projectedFits_table = Table("projectedFits", saveddata_meta, Column("sourceID", ForeignKey("fits.ID"), primary_key = True), Column("victimID", ForeignKey("fits.ID"), primary_key = True), - Column("amount", Integer)) + Column("amount", Integer), + Column("active", Boolean), +) class ProjectedFit(object): - def __init__(self, source_item, dest_item, enabled): - print "init projected item", source_item, dest_item, enabled + def __init__(self, source_item, dest_item, amount=1, active=True): + print "init projected item", source_item, dest_item, active, amount self.source_item = source_item self.dest_item = dest_item - self.amount = enabled + self.amount = amount + self.active = active + self.dest_item.projectionInfo = self @reconstructor def init(self): @@ -65,7 +69,7 @@ class ProjectedFit(object): Fit._Fit__projectedFits = association_proxy( "projected_items", "dest_item", - creator=lambda dest_item: ProjectedFit(None, dest_item, True) + creator=lambda dest_item: ProjectedFit(None, dest_item) ) mapper(Fit, fits_table, diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index b0b94c2df..f21e8539d 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -461,8 +461,9 @@ class Fit(object): item.calculateModifiedAttributes(targetFit, runTime, True) for fit in self.projectedFits: - if fit.projectionInfo.amount > 0: - fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) + if fit.projectionInfo.active: + #for _ in xrange(fit.projectionInfo.amount): + fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) def fill(self): """ diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index a6b3fe49f..0bd0e8c43 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -39,7 +39,7 @@ class BaseName(ViewColumn): elif isinstance(stuff, Cargo): return "%dx %s" % (stuff.amount, stuff.item.name) elif isinstance(stuff, Fit): - return "%s (%s)" % (stuff.name, stuff.ship.item.name) + return "%dx %s (%s)" % (stuff.projectionInfo.amount, stuff.name, stuff.ship.item.name) elif isinstance(stuff, Rack): if service.Fit.getInstance().serviceFittingOptions["rackLabels"]: if stuff.slot == Slot.MODE: diff --git a/gui/builtinViewColumns/state.py b/gui/builtinViewColumns/state.py index c10a0f77f..a0e8258ee 100644 --- a/gui/builtinViewColumns/state.py +++ b/gui/builtinViewColumns/state.py @@ -67,7 +67,7 @@ class State(ViewColumn): elif isinstance(stuff, Fit): if stuff.projectionInfo is None: return -1 - if stuff.projectionInfo.amount > 0: + if stuff.projectionInfo.active: return generic_active return generic_inactive else: diff --git a/service/fit.py b/service/fit.py index 9ce47ad62..d328be6ed 100644 --- a/service/fit.py +++ b/service/fit.py @@ -365,7 +365,7 @@ class Fit(object): thing.state = State.OFFLINE elif isinstance(thing, eos.types.Fit): print "toggle fit" - thing.projectionInfo.amount = not thing.projectionInfo.amount + thing.projectionInfo.active = not thing.projectionInfo.active eos.db.commit() self.recalc(fit) From 06e4a7e80f6195122c6f7a9dd95cd66047d85a44 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 7 Jul 2015 01:57:59 -0400 Subject: [PATCH 04/34] Support changing amount of projected fits --- gui/builtinContextMenus/__init__.py | 3 +- gui/builtinContextMenus/amount.py | 76 +++++++++++++++++++++++++++++ gui/builtinContextMenus/cargo.py | 64 ------------------------ service/fit.py | 13 ++++- 4 files changed, 90 insertions(+), 66 deletions(-) create mode 100644 gui/builtinContextMenus/amount.py diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py index 7ce9ed894..b9fb3386d 100644 --- a/gui/builtinContextMenus/__init__.py +++ b/gui/builtinContextMenus/__init__.py @@ -17,5 +17,6 @@ __all__ = [ #"changeAffectingSkills", "tacticalMode", "targetResists", - "priceClear" + "priceClear", + "amount", ] diff --git a/gui/builtinContextMenus/amount.py b/gui/builtinContextMenus/amount.py new file mode 100644 index 000000000..a8e00a28f --- /dev/null +++ b/gui/builtinContextMenus/amount.py @@ -0,0 +1,76 @@ +from gui.contextMenu import ContextMenu +from gui.itemStats import ItemStatsDialog +import eos.types +import gui.mainFrame +import service +import gui.globalEvents as GE +import wx + +class ChangeAmount(ContextMenu): + def __init__(self): + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + + def display(self, srcContext, selection): + return srcContext in ("cargoItem","projectedFit") + + def getText(self, itmContext, selection): + print selection + return "Change {0} Quantity".format(itmContext) + + def activate(self, fullContext, selection, i): + srcContext = fullContext[0] + dlg = AmountChanger(self.mainFrame, selection[0], srcContext) + dlg.ShowModal() + dlg.Destroy() + +ChangeAmount.register() + +class AmountChanger(wx.Dialog): + + def __init__(self, parent, thing, context): + wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60)) + self.thing = thing + self.context = context + + bSizer1 = wx.BoxSizer(wx.HORIZONTAL) + + self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) + + bSizer1.Add(self.input, 1, wx.ALL, 5) + self.input.Bind(wx.EVT_CHAR, self.onChar) + self.input.Bind(wx.EVT_TEXT_ENTER, self.change) + self.button = wx.Button(self, wx.ID_OK, u"Done") + bSizer1.Add(self.button, 0, wx.ALL, 5) + + self.SetSizer(bSizer1) + self.Layout() + self.Centre(wx.BOTH) + self.button.Bind(wx.EVT_BUTTON, self.change) + + def change(self, event): + sFit = service.Fit.getInstance() + mainFrame = gui.mainFrame.MainFrame.getInstance() + fitID = mainFrame.getActiveFit() + + if isinstance(self.thing, eos.types.Cargo): + sFit.addCargo(fitID, self.thing.item.ID, int(self.input.GetLineText(0)), replace=True) + elif isinstance(self.thing, eos.types.Fit): + sFit.changeAmount(fitID, self.thing, int(self.input.GetLineText(0))) + + wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID)) + + event.Skip() + self.Destroy() + + ## checks to make sure it's valid number + def onChar(self, event): + key = event.GetKeyCode() + + acceptable_characters = "1234567890" + acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste + if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters): + event.Skip() + return + else: + return False + diff --git a/gui/builtinContextMenus/cargo.py b/gui/builtinContextMenus/cargo.py index 6eda67458..3cc86902b 100644 --- a/gui/builtinContextMenus/cargo.py +++ b/gui/builtinContextMenus/cargo.py @@ -29,67 +29,3 @@ class Cargo(ContextMenu): wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) Cargo.register() - -class CargoAmount(ContextMenu): - def __init__(self): - self.mainFrame = gui.mainFrame.MainFrame.getInstance() - - def display(self, srcContext, selection): - return srcContext in ("cargoItem",) and selection[0].amount >= 0 - - def getText(self, itmContext, selection): - return "Change {0} Quantity".format(itmContext) - - def activate(self, fullContext, selection, i): - srcContext = fullContext[0] - dlg = CargoChanger(self.mainFrame, selection[0], srcContext) - dlg.ShowModal() - dlg.Destroy() - -CargoAmount.register() - -class CargoChanger(wx.Dialog): - - def __init__(self, parent, cargo, context): - wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60)) - self.cargo = cargo - self.context = context - - bSizer1 = wx.BoxSizer(wx.HORIZONTAL) - - self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) - - bSizer1.Add(self.input, 1, wx.ALL, 5) - self.input.Bind(wx.EVT_CHAR, self.onChar) - self.input.Bind(wx.EVT_TEXT_ENTER, self.change) - self.button = wx.Button(self, wx.ID_OK, u"Done") - bSizer1.Add(self.button, 0, wx.ALL, 5) - - self.SetSizer(bSizer1) - self.Layout() - self.Centre(wx.BOTH) - self.button.Bind(wx.EVT_BUTTON, self.change) - - def change(self, event): - sFit = service.Fit.getInstance() - mainFrame = gui.mainFrame.MainFrame.getInstance() - fitID = mainFrame.getActiveFit() - - sFit.addCargo(fitID, self.cargo.item.ID, int(self.input.GetLineText(0)), replace=True) - - wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID)) - - event.Skip() - self.Destroy() - ## checks to make sure it's valid number - def onChar(self, event): - key = event.GetKeyCode() - - acceptable_characters = "1234567890" - acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste - if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters): - event.Skip() - return - else: - return False - diff --git a/service/fit.py b/service/fit.py index d328be6ed..5102a8bc5 100644 --- a/service/fit.py +++ b/service/fit.py @@ -363,13 +363,24 @@ class Fit(object): thing.state = self.__getProposedState(thing, click) if not thing.canHaveState(thing.state, fit): thing.state = State.OFFLINE - elif isinstance(thing, eos.types.Fit): + elif isinstance(thing, eos.types.Fit) and thing.projectionInfo is not None: print "toggle fit" thing.projectionInfo.active = not thing.projectionInfo.active eos.db.commit() self.recalc(fit) + def changeAmount(self, fitID, projected_fit, amount): + """Change amount of projected fits""" + fit = eos.db.getFit(fitID) + amount = min(5, max(1, amount)) # 1 <= a <= 5 + + if projected_fit.projectionInfo is not None: + projected_fit.projectionInfo.amount = amount + + eos.db.commit() + self.recalc(fit) + def removeProjected(self, fitID, thing): fit = eos.db.getFit(fitID) if isinstance(thing, eos.types.Drone): From 23309a5da6c8bee37d9d10878c66c55421b73ad4 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 7 Jul 2015 13:22:59 -0400 Subject: [PATCH 05/34] Remove unneeded code that created a bitmap for checkboxes --- gui/builtinViewColumns/state.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gui/builtinViewColumns/state.py b/gui/builtinViewColumns/state.py index a0e8258ee..0925d2f2d 100644 --- a/gui/builtinViewColumns/state.py +++ b/gui/builtinViewColumns/state.py @@ -31,15 +31,6 @@ class State(ViewColumn): self.size = 16 self.maxsize = self.size self.mask = wx.LIST_MASK_IMAGE - for name, state in (("checked", wx.CONTROL_CHECKED), ("unchecked", 0)): - bitmap = wx.EmptyBitmap(16, 16) - dc = wx.MemoryDC() - dc.SelectObject(bitmap) - dc.SetBackground(wx.TheBrushList.FindOrCreateBrush(fittingView.GetBackgroundColour(), wx.SOLID)) - dc.Clear() - wx.RendererNative.Get().DrawCheckBox(fittingView, dc, wx.Rect(0, 0, 16, 16), state) - dc.Destroy() - setattr(self, "%sId" % name, fittingView.imageList.Add(bitmap)) def getText(self, mod): return "" From c17bce55bbe68384c3a53a22b8076f9ab46d0d05 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 7 Jul 2015 21:48:04 -0400 Subject: [PATCH 06/34] Lots of stuff - Added logging and replaced Timer class with more useful one - Move projected fit loop out of runtimes - Eliminate recursions from Fit.clear() - Clean up overall fit calc logic --- config.py | 2 +- eos/saveddata/fit.py | 144 +++++++++++++++++++++++++++---------------- service/fit.py | 8 ++- utils/timer.py | 48 ++++++++------- 4 files changed, 124 insertions(+), 78 deletions(-) diff --git a/config.py b/config.py index c20489972..a33ec451a 100644 --- a/config.py +++ b/config.py @@ -17,7 +17,7 @@ debug = False # Defines if our saveddata will be in pyfa root or not saveInRoot = False -logLevel = logging.WARN +logLevel = logging.DEBUG # Version data version = "1.12.1" diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index f21e8539d..3edb73a30 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -29,6 +29,8 @@ from eos.saveddata.module import State from eos.saveddata.mode import Mode import eos.db import time +from utils.timer import Timer + import logging logger = logging.getLogger(__name__) @@ -353,10 +355,28 @@ class Fit(object): del self.__calculatedTargets[:] del self.__extraDrains[:] - if self.ship is not None: self.ship.clear() - c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes)) + if self.ship: + self.ship.clear() + + c = chain( + self.modules, + self.drones, + self.boosters, + self.implants, + self.projectedDrones, + self.projectedModules, + (self.character, self.extraAttributes), + ) + + # If we are in a root ship, add projected fits to clear list + # Do not add projected fits if self is already projected - this can + # cause infinite recursion in projection loop, eg A > B > C > A + if self.projectionInfo is None: + c = chain(c, self.projectedFits) + for stuff in c: - if stuff is not None and stuff != self: stuff.clear() + if stuff is not None and stuff != self: + stuff.clear() #Methods to register and get the thing currently affecting the fit, #so we can correctly map "Affected By" @@ -370,7 +390,38 @@ class Fit(object): def getModifier(self): return self.__modifier + def __calculateGangBoosts(self, runTime): + for name, info in self.gangBoosts.iteritems(): + # Unpack all data required to run effect properly + effect, thing = info[1] + if effect.runTime == runTime: + context = ("gang", thing.__class__.__name__.lower()) + if isinstance(thing, Module): + if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \ + (effect.isType("active") and thing.state >= State.ACTIVE): + # Run effect, and get proper bonuses applied + try: + self.register(thing) + effect.handler(self, thing, context) + except: + pass + else: + # Run effect, and get proper bonuses applied + try: + self.register(thing) + effect.handler(self, thing, context) + except: + pass + def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): + timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger) + logger.debug("Starting fit calculation on: %d %s (%s)" % + (self.ID, self.name, self.ship.item.name)) + + if targetFit: + logger.debug("Applying projections to target: %d %s (%s)" % + (targetFit.ID, targetFit.name, targetFit.ship.item.name)) + refreshBoosts = False if withBoosters is True: refreshBoosts = True @@ -379,6 +430,7 @@ class Fit(object): if dirtyStorage is not None: dirtyStorage.update(self.boostsFits) if self.fleet is not None and refreshBoosts is True: + logger.debug("Fleet is set, gathering gang boosts") self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage) elif self.fleet is None: self.gangBoosts = None @@ -387,84 +439,70 @@ class Fit(object): dirtyStorage.remove(self.ID) except KeyError: pass + # If we're not explicitly asked to project fit onto something, # set self as target fit if targetFit is None: targetFit = self - forceProjected = False + projected = False # Else, we're checking all target projectee fits elif targetFit not in self.__calculatedTargets: + logger.debug("Target fit has not been calculated, calculating first") + # target fit is required to be calculated before we do projections + # @todo: is there any situation where a projected ship would get here and the targte fit not be calculated already? See if we can get rid of this block of code self.__calculatedTargets.append(targetFit) targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage) - forceProjected = True + projected = True # Or do nothing if target fit is calculated else: return # If fit is calculated and we have nothing to do here, get out - if self.__calculated == True and forceProjected == False: + if self.__calculated and not projected: + logger.debug("Fit has already been calculated and is not projected, returning") return # Mark fit as calculated self.__calculated = True - # There's a few things to keep in mind here - # 1: Early effects first, then regular ones, then late ones, regardless of anything else - # 2: Some effects aren't implemented - # 3: Some effects are implemented poorly and will just explode on us - # 4: Errors should be handled gracefully and preferably without crashing unless serious - for runTime in ("early", "normal", "late"): - # Build a little chain of stuff - # Avoid adding projected drones and modules when fit is projected onto self - # TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented - if forceProjected is True: - # if fit is being projected onto another fit - c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules) - else: - c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules, - self.projectedDrones, self.projectedModules) + c = chain( + (self.character, self.ship), + self.drones, + self.boosters, + self.appliedImplants, + self.modules + ) + if not projected: + # if not a projected fit, add a couple of more things + c = chain(c, self.projectedDrones, self.projectedModules) + + for runTime in ("early", "normal", "late"): + # We calculate gang bonuses first so that projected fits get them if self.gangBoosts is not None: - contextMap = {Skill: "skill", - Ship: "ship", - Module: "module", - Implant: "implant"} - for name, info in self.gangBoosts.iteritems(): - # Unpack all data required to run effect properly - effect, thing = info[1] - if effect.runTime == runTime: - context = ("gang", contextMap[type(thing)]) - if isinstance(thing, Module): - if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \ - (effect.isType("active") and thing.state >= State.ACTIVE): - # Run effect, and get proper bonuses applied - try: - self.register(thing) - effect.handler(self, thing, context) - except: - pass - else: - # Run effect, and get proper bonuses applied - try: - self.register(thing) - effect.handler(self, thing, context) - except: - pass + self.__calculateGangBoosts(runTime) for item in c: - # Registering the item about to affect the fit allows us to track "Affected By" relations correctly + # Registering the item about to affect the fit allows us to + # track "Affected By" relations correctly if item is not None: self.register(item) item.calculateModifiedAttributes(self, runTime, False) - if forceProjected is True: - targetFit.register(item) - item.calculateModifiedAttributes(targetFit, runTime, True) + if projected is True: + for _ in xrange(self.projectionInfo.amount): + targetFit.register(item) + item.calculateModifiedAttributes(targetFit, runTime, True) - for fit in self.projectedFits: - if fit.projectionInfo.active: - #for _ in xrange(fit.projectionInfo.amount): + timer.checkpoint('Done with runtime: %s'%runTime) + + # Only apply projected fits if fit it not projected itself. + if not projected: + for fit in self.projectedFits: + if fit.projectionInfo.active: fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) + timer.checkpoint('Done with fit calculation') + def fill(self): """ Fill this fit's module slots with enough dummy slots so that all slots are used. diff --git a/service/fit.py b/service/fit.py index 5102a8bc5..dddc4ebfb 100644 --- a/service/fit.py +++ b/service/fit.py @@ -36,7 +36,7 @@ from service.fleet import Fleet from service.settings import SettingsProvider from service.port import Port -logger = logging.getLogger("pyfa.service.fit") +logger = logging.getLogger(__name__) class FitBackupThread(threading.Thread): def __init__(self, path, callback): @@ -236,6 +236,7 @@ class Fit(object): return None fit = eos.db.getFit(fitID) inited = getattr(fit, "inited", None) + if inited is None or inited is False: sFleet = Fleet.getInstance() f = sFleet.getLinearFleet(fit) @@ -373,7 +374,7 @@ class Fit(object): def changeAmount(self, fitID, projected_fit, amount): """Change amount of projected fits""" fit = eos.db.getFit(fitID) - amount = min(5, max(1, amount)) # 1 <= a <= 5 + amount = min(20, max(1, amount)) # 1 <= a <= 5 if projected_fit.projectionInfo is not None: projected_fit.projectionInfo.amount = amount @@ -936,7 +937,8 @@ class Fit(object): self.recalc(fit) def recalc(self, fit, withBoosters=False): + logger.debug("="*10+"recalc"+"="*10) if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] - fit.clear() + fit.clear() fit.calculateModifiedAttributes(withBoosters=withBoosters, dirtyStorage=self.dirtyFitIDs) diff --git a/utils/timer.py b/utils/timer.py index 811dfe348..da355cba9 100644 --- a/utils/timer.py +++ b/utils/timer.py @@ -1,30 +1,36 @@ import time -class Timer(object): - """ - Generic timing class for simple profiling. +class Timer(): + def __init__(self, name='', logger=None): + self.name = name + self.start = time.time() + self.__last = self.start + self.logger = logger - Usage: + @property + def elapsed(self): + return (time.time() - self.start)*1000 - with Timer(verbose=True) as t: - # code to be timed - time.sleep(5) + @property + def last(self): + return (time.time() - self.__last)*1000 - Output: - elapsed time: 5000.000 ms - - Can also access time with t.secs - """ - def __init__(self, verbose=False): - self.verbose = verbose + def checkpoint(self, name=''): + text = 'Timer - {timer} - {checkpoint} - {last:.2f}ms ({elapsed:.2f}ms elapsed)'.format( + timer=self.name, + checkpoint=name, + last=self.last, + elapsed=self.elapsed + ).strip() + self.__last = time.time() + if self.logger: + self.logger.debug(text) + else: + print text def __enter__(self): - self.start = time.time() return self - def __exit__(self, *args): - self.end = time.time() - self.secs = self.end - self.start - self.msecs = self.secs * 1000 # millisecs - if self.verbose: - print 'elapsed time: %f ms' % self.msecs \ No newline at end of file + def __exit__(self, type, value, traceback): + self.checkpoint('finished') + pass From af9f64db5f841ef6f402fe72327412dc43067ece Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 9 Jul 2015 14:48:15 -0400 Subject: [PATCH 07/34] Move the chain into the runtime loop, otherwise projections won't work for some odd reason. --- eos/saveddata/fit.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 3edb73a30..391f046b8 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -465,19 +465,19 @@ class Fit(object): # Mark fit as calculated self.__calculated = True - c = chain( - (self.character, self.ship), - self.drones, - self.boosters, - self.appliedImplants, - self.modules - ) - - if not projected: - # if not a projected fit, add a couple of more things - c = chain(c, self.projectedDrones, self.projectedModules) - for runTime in ("early", "normal", "late"): + c = chain( + (self.character, self.ship), + self.drones, + self.boosters, + self.appliedImplants, + self.modules + ) + + if not projected: + # if not a projected fit, add a couple of more things + c = chain(c, self.projectedDrones, self.projectedModules) + # We calculate gang bonuses first so that projected fits get them if self.gangBoosts is not None: self.__calculateGangBoosts(runTime) From c17e03d8d01147635ff81b79b46e3e05a31f9984 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 9 Jul 2015 17:53:41 -0400 Subject: [PATCH 08/34] Fixes critical design issue when it comes to projected fits. Disabled some of the more advanced functionality (projection amount and active) to cope to development. Crash still happens occasionally when adding projected fit for unknown reasons - not 100% reproducable yet --- eos/db/saveddata/fit.py | 39 +++++++++++++++++------------- eos/saveddata/fit.py | 32 ++++++++++++------------ gui/builtinViewColumns/baseName.py | 6 ++++- gui/builtinViewColumns/state.py | 10 ++++++-- service/fit.py | 16 ++++++------ 5 files changed, 61 insertions(+), 42 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 4c03bdd32..6b4e44fb4 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -21,6 +21,7 @@ from sqlalchemy import * from sqlalchemy.orm import * from sqlalchemy.sql import and_ from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.orm.collections import attribute_mapped_collection from eos.db import saveddata_meta from eos.db.saveddata.module import modules_table @@ -51,25 +52,27 @@ projectedFits_table = Table("projectedFits", saveddata_meta, ) class ProjectedFit(object): - def __init__(self, source_item, dest_item, amount=1, active=True): - print "init projected item", source_item, dest_item, active, amount - self.source_item = source_item - self.dest_item = dest_item + def __init__(self, source_fit, k, amount=1, active=True): + print "init projected: ", k, source_fit.name, active, amount + self.sourceID = k + self.source_item = source_fit + self.victim_item = None self.amount = amount self.active = active - self.dest_item.projectionInfo = self + #self.dest_item.setProjectionInfo(self.source_item.ID, self) @reconstructor def init(self): print "db init" - print "\t source:",self.source_item - print "\t dest:", self.dest_item - self.dest_item.projectionInfo = self + print "\t source:", self.source_fit + print "\t dest:", self.victim_fit + #self.dest_item.setProjectionInfo(self.source_item.ID, self) + #print self.dest_item.ship.item.name, ">", self.source_item.ship.item.name,self Fit._Fit__projectedFits = association_proxy( - "projected_items", - "dest_item", - creator=lambda dest_item: ProjectedFit(None, dest_item) + "victimOf", # look at the victimOf association... + "source_fit", # .. and return the source fits + creator=lambda k, victim_fit: ProjectedFit(victim_fit, k) ) mapper(Fit, fits_table, @@ -128,15 +131,17 @@ mapper(Fit, fits_table, backref="fits"), "_Fit__damagePattern": relation(DamagePattern), "_Fit__targetResists": relation(TargetResists), - "dest_items": relationship( + "projectedOnto": relationship( ProjectedFit, - primaryjoin=projectedFits_table.c.victimID == fits_table.c.ID, - backref='dest_item', + primaryjoin=projectedFits_table.c.sourceID == fits_table.c.ID, + backref='source_fit', + collection_class=attribute_mapped_collection('victimID'), cascade='all, delete, delete-orphan'), - "projected_items": relationship( + "victimOf": relationship( ProjectedFit, - primaryjoin=fits_table.c.ID == projectedFits_table.c.sourceID, - backref='source_item', + primaryjoin=fits_table.c.ID == projectedFits_table.c.victimID, + backref='victim_fit', + collection_class=attribute_mapped_collection('sourceID'), cascade='all, delete, delete-orphan'), } ) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 391f046b8..9c76dee23 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -119,7 +119,7 @@ class Fit(object): self.__capUsed = None self.__capRecharge = None self.__calculatedTargets = [] - self.__projectionInfo = None + self.__projectionMap = {} self.factorReload = False self.fleet = None self.boostsFits = set() @@ -208,15 +208,10 @@ class Fit(object): @property def projectedFits(self): - return self.__projectedFits + return self.__projectedFits.values() - @property - def projectionInfo(self): - return self.__projectionInfo - - @projectionInfo.setter - def projectionInfo(self, projectionInfo): - self.__projectionInfo = projectionInfo + def getProjectionInfo(self, fitID): + return self.projectedOnto.get(fitID, None) @property def projectedDrones(self): @@ -371,7 +366,11 @@ class Fit(object): # If we are in a root ship, add projected fits to clear list # Do not add projected fits if self is already projected - this can # cause infinite recursion in projection loop, eg A > B > C > A - if self.projectionInfo is None: + # @todo: since fits persist, we need to be sure that even if a fit has + # projection info (loaded as a projected fit), this still happens when + # we clear the fit if the fit is the root + #if self.projectionInfo is None: + if True: c = chain(c, self.projectedFits) for stuff in c: @@ -417,10 +416,13 @@ class Fit(object): timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger) logger.debug("Starting fit calculation on: %d %s (%s)" % (self.ID, self.name, self.ship.item.name)) - + #print self.projectedFits if targetFit: - logger.debug("Applying projections to target: %d %s (%s)" % - (targetFit.ID, targetFit.name, targetFit.ship.item.name)) + logger.debug("Applying projections to target: %d %s (%s)", + targetFit.ID, targetFit.name, targetFit.ship.item.name) + projectionInfo = self.getProjectionInfo(targetFit.ID) + logger.debug("ProjectionInfo: amount=%s, active=%s, instance=%s", + projectionInfo.amount, projectionInfo.active, projectionInfo) refreshBoosts = False if withBoosters is True: @@ -489,7 +491,7 @@ class Fit(object): self.register(item) item.calculateModifiedAttributes(self, runTime, False) if projected is True: - for _ in xrange(self.projectionInfo.amount): + #for _ in xrange(projectionInfo.amount): targetFit.register(item) item.calculateModifiedAttributes(targetFit, runTime, True) @@ -498,7 +500,7 @@ class Fit(object): # Only apply projected fits if fit it not projected itself. if not projected: for fit in self.projectedFits: - if fit.projectionInfo.active: + #if fit.getProjectionInfo(self.ID).active: fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) timer.checkpoint('Done with fit calculation') diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index 0bd0e8c43..cf9222414 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -21,6 +21,8 @@ from gui import builtinViewColumns from gui.viewColumn import ViewColumn from gui import bitmapLoader +import gui.mainFrame + import wx from eos.types import Drone, Cargo, Fit, Module, Slot, Rack import service @@ -29,6 +31,7 @@ class BaseName(ViewColumn): name = "Base Name" def __init__(self, fittingView, params): ViewColumn.__init__(self, fittingView) + self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.columnText = "Name" self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons") self.mask = wx.LIST_MASK_TEXT @@ -39,7 +42,8 @@ class BaseName(ViewColumn): elif isinstance(stuff, Cargo): return "%dx %s" % (stuff.amount, stuff.item.name) elif isinstance(stuff, Fit): - return "%dx %s (%s)" % (stuff.projectionInfo.amount, stuff.name, stuff.ship.item.name) + fitID = self.mainFrame.getActiveFit() + return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name) elif isinstance(stuff, Rack): if service.Fit.getInstance().serviceFittingOptions["rackLabels"]: if stuff.slot == Slot.MODE: diff --git a/gui/builtinViewColumns/state.py b/gui/builtinViewColumns/state.py index 0925d2f2d..8110a85ad 100644 --- a/gui/builtinViewColumns/state.py +++ b/gui/builtinViewColumns/state.py @@ -19,6 +19,8 @@ from gui.viewColumn import ViewColumn from gui import bitmapLoader +import gui.mainFrame + import wx from eos.types import Drone, Module, Rack, Fit from eos.types import State as State_ @@ -27,6 +29,7 @@ class State(ViewColumn): name = "State" def __init__(self, fittingView, params): ViewColumn.__init__(self, fittingView) + self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.resizable = False self.size = 16 self.maxsize = self.size @@ -56,9 +59,12 @@ class State(ViewColumn): else: return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(stuff.state).lower(), "icons") elif isinstance(stuff, Fit): - if stuff.projectionInfo is None: + fitID = self.mainFrame.getActiveFit() + projectionInfo = stuff.getProjectionInfo(fitID) + + if projectionInfo is None: return -1 - if stuff.projectionInfo.active: + if projectionInfo.active: return generic_active return generic_inactive else: diff --git a/service/fit.py b/service/fit.py index dddc4ebfb..9effe43a6 100644 --- a/service/fit.py +++ b/service/fit.py @@ -325,7 +325,8 @@ class Fit(object): if isinstance(thing, eos.types.Fit): if thing.ID == fitID: return - fit.projectedFits.append(thing) + + fit.__projectedFits[thing.ID] = thing elif thing.category.name == "Drone": drone = None for d in fit.projectedDrones.find(thing): @@ -374,10 +375,10 @@ class Fit(object): def changeAmount(self, fitID, projected_fit, amount): """Change amount of projected fits""" fit = eos.db.getFit(fitID) - amount = min(20, max(1, amount)) # 1 <= a <= 5 - - if projected_fit.projectionInfo is not None: - projected_fit.projectionInfo.amount = amount + amount = min(20, max(1, amount)) # 1 <= a <= 20 + projectionInfo = projected_fit.getProjectionInfo(fitID) + if projectionInfo: + projectionInfo.amount = amount eos.db.commit() self.recalc(fit) @@ -389,7 +390,8 @@ class Fit(object): elif isinstance(thing, eos.types.Module): fit.projectedModules.remove(thing) else: - fit.projectedFits.remove(thing) + del fit.__projectedFits[thing.ID] + #fit.projectedFits.remove(thing) eos.db.commit() self.recalc(fit) @@ -937,7 +939,7 @@ class Fit(object): self.recalc(fit) def recalc(self, fit, withBoosters=False): - logger.debug("="*10+"recalc"+"="*10) + logger.debug("="*10+"recalc"+"="*10) if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] fit.clear() From 68dddf2810cb1d8404ca09641f0ceb3c1cc8b95c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 11:58:15 -0400 Subject: [PATCH 09/34] Fix for init projected fit. Took a long time to figure out what was happening. --- eos/db/saveddata/fit.py | 11 ++++------- eos/saveddata/fit.py | 33 +++++++++++++++++---------------- service/fit.py | 6 +++++- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 6b4e44fb4..b355ba4f1 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -53,26 +53,23 @@ projectedFits_table = Table("projectedFits", saveddata_meta, class ProjectedFit(object): def __init__(self, source_fit, k, amount=1, active=True): - print "init projected: ", k, source_fit.name, active, amount + #print "init projected: source fit: ", source_fit.name, source_fit, "key (fit ID)", key, "active:", active, "amount:",amount self.sourceID = k - self.source_item = source_fit - self.victim_item = None + self.source_fit = source_fit + #self.victim_item = None self.amount = amount self.active = active - #self.dest_item.setProjectionInfo(self.source_item.ID, self) @reconstructor def init(self): print "db init" print "\t source:", self.source_fit print "\t dest:", self.victim_fit - #self.dest_item.setProjectionInfo(self.source_item.ID, self) - #print self.dest_item.ship.item.name, ">", self.source_item.ship.item.name,self Fit._Fit__projectedFits = association_proxy( "victimOf", # look at the victimOf association... "source_fit", # .. and return the source fits - creator=lambda k, victim_fit: ProjectedFit(victim_fit, k) + creator=lambda k, source_fit: ProjectedFit(source_fit, k) ) mapper(Fit, fits_table, diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 9c76dee23..e1f943d05 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -63,7 +63,7 @@ class Fit(object): self.__cargo = HandledDroneCargoList() self.__implants = HandledImplantBoosterList() self.__boosters = HandledImplantBoosterList() - self.__projectedFits = HandledProjectedFitList() + #self.__projectedFits = {} self.__projectedModules = HandledProjectedModList() self.__projectedDrones = HandledProjectedDroneList() self.__character = None @@ -119,7 +119,6 @@ class Fit(object): self.__capUsed = None self.__capRecharge = None self.__calculatedTargets = [] - self.__projectionMap = {} self.factorReload = False self.fleet = None self.boostsFits = set() @@ -208,9 +207,11 @@ class Fit(object): @property def projectedFits(self): + #print "get projected fits for :", self.name return self.__projectedFits.values() def getProjectionInfo(self, fitID): + print "get projection info for fitID: ", fitID return self.projectedOnto.get(fitID, None) @property @@ -416,13 +417,15 @@ class Fit(object): timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger) logger.debug("Starting fit calculation on: %d %s (%s)" % (self.ID, self.name, self.ship.item.name)) + print self + #print self.__projectedFits #print self.projectedFits if targetFit: logger.debug("Applying projections to target: %d %s (%s)", targetFit.ID, targetFit.name, targetFit.ship.item.name) - projectionInfo = self.getProjectionInfo(targetFit.ID) - logger.debug("ProjectionInfo: amount=%s, active=%s, instance=%s", - projectionInfo.amount, projectionInfo.active, projectionInfo) + #projectionInfo = self.getProjectionInfo(targetFit.ID) + #logger.debug("ProjectionInfo: amount=%s, active=%s, instance=%s", + # projectionInfo.amount, projectionInfo.active, projectionInfo) refreshBoosts = False if withBoosters is True: @@ -447,23 +450,21 @@ class Fit(object): if targetFit is None: targetFit = self projected = False - # Else, we're checking all target projectee fits - elif targetFit not in self.__calculatedTargets: - logger.debug("Target fit has not been calculated, calculating first") - # target fit is required to be calculated before we do projections - # @todo: is there any situation where a projected ship would get here and the targte fit not be calculated already? See if we can get rid of this block of code - self.__calculatedTargets.append(targetFit) - targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage) - projected = True - # Or do nothing if target fit is calculated else: - return + projected = True # If fit is calculated and we have nothing to do here, get out if self.__calculated and not projected: logger.debug("Fit has already been calculated and is not projected, returning") return - + print + print "******** projected fits ********" + print self.__projectedFits + print "******** projectedOnto ********" + print self.projectedOnto + print "******** victimOf ********" + print self.victimOf + print # Mark fit as calculated self.__calculated = True diff --git a/service/fit.py b/service/fit.py index 9effe43a6..7b4f6e406 100644 --- a/service/fit.py +++ b/service/fit.py @@ -327,6 +327,10 @@ class Fit(object): return fit.__projectedFits[thing.ID] = thing + + # this bit is required -- see GH issue + eos.db.saveddata_session.flush() + eos.db.saveddata_session.refresh(thing) elif thing.category.name == "Drone": drone = None for d in fit.projectedDrones.find(thing): @@ -939,7 +943,7 @@ class Fit(object): self.recalc(fit) def recalc(self, fit, withBoosters=False): - logger.debug("="*10+"recalc"+"="*10) + logger.debug("="*10+"recalc"+"="*10) if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] fit.clear() From 496e9b56b5b69ad218882b6eab6578001962376d Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 15:45:36 -0400 Subject: [PATCH 10/34] Handle use case of invalid fit's mucking things up --- eos/db/saveddata/fit.py | 16 +++++++------- eos/saveddata/fit.py | 46 +++++++++++++++-------------------------- service/fit.py | 14 +++++++++---- 3 files changed, 35 insertions(+), 41 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index b355ba4f1..744a849a0 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -52,24 +52,24 @@ projectedFits_table = Table("projectedFits", saveddata_meta, ) class ProjectedFit(object): - def __init__(self, source_fit, k, amount=1, active=True): - #print "init projected: source fit: ", source_fit.name, source_fit, "key (fit ID)", key, "active:", active, "amount:",amount - self.sourceID = k + def __init__(self, sourceID, source_fit, amount=1, active=True): + self.sourceID = sourceID self.source_fit = source_fit - #self.victim_item = None self.amount = amount self.active = active @reconstructor def init(self): - print "db init" - print "\t source:", self.source_fit - print "\t dest:", self.victim_fit + if self.source_fit.isInvalid: + # Very rare for this to happen, but be prepared for it + eos.db.saveddata_session.delete(self.source_fit) + eos.db.saveddata_session.flush() + eos.db.saveddata_session.refresh(self.victim_fit) Fit._Fit__projectedFits = association_proxy( "victimOf", # look at the victimOf association... "source_fit", # .. and return the source fits - creator=lambda k, source_fit: ProjectedFit(source_fit, k) + creator=lambda sourceID, source_fit: ProjectedFit(sourceID, source_fit) ) mapper(Fit, fits_table, diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index e1f943d05..2ded319b7 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -207,8 +207,9 @@ class Fit(object): @property def projectedFits(self): - #print "get projected fits for :", self.name - return self.__projectedFits.values() + # only in extreme edge cases will the fit be invalid, but to be sure do + # not return them. By this time + return [fit for fit in self.__projectedFits.values() if not fit.isInvalid] def getProjectionInfo(self, fitID): print "get projection info for fitID: ", fitID @@ -331,7 +332,7 @@ class Fit(object): if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key) else: return val - def clear(self): + def clear(self, projected=False): self.__effectiveTank = None self.__weaponDPS = None self.__minerYield = None @@ -364,20 +365,18 @@ class Fit(object): (self.character, self.extraAttributes), ) - # If we are in a root ship, add projected fits to clear list - # Do not add projected fits if self is already projected - this can - # cause infinite recursion in projection loop, eg A > B > C > A - # @todo: since fits persist, we need to be sure that even if a fit has - # projection info (loaded as a projected fit), this still happens when - # we clear the fit if the fit is the root - #if self.projectionInfo is None: - if True: - c = chain(c, self.projectedFits) - for stuff in c: if stuff is not None and stuff != self: stuff.clear() + # If this is the active fit that we are clearing, not a projected fit, + # then this will run and clear the projected ships and flag the next + # iteration to skip this part to prevent recursion. + if not projected: + for stuff in self.projectedFits: + if stuff is not None and stuff != self: + stuff.clear(projected=True) + #Methods to register and get the thing currently affecting the fit, #so we can correctly map "Affected By" def register(self, currModifier): @@ -417,15 +416,11 @@ class Fit(object): timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger) logger.debug("Starting fit calculation on: %d %s (%s)" % (self.ID, self.name, self.ship.item.name)) - print self - #print self.__projectedFits - #print self.projectedFits if targetFit: logger.debug("Applying projections to target: %d %s (%s)", targetFit.ID, targetFit.name, targetFit.ship.item.name) - #projectionInfo = self.getProjectionInfo(targetFit.ID) - #logger.debug("ProjectionInfo: amount=%s, active=%s, instance=%s", - # projectionInfo.amount, projectionInfo.active, projectionInfo) + projectionInfo = self.getProjectionInfo(targetFit.ID) + logger.debug("ProjectionInfo: %s", ', '.join("%s: %s" % item for item in vars(projectionInfo).items())) refreshBoosts = False if withBoosters is True: @@ -457,14 +452,7 @@ class Fit(object): if self.__calculated and not projected: logger.debug("Fit has already been calculated and is not projected, returning") return - print - print "******** projected fits ********" - print self.__projectedFits - print "******** projectedOnto ********" - print self.projectedOnto - print "******** victimOf ********" - print self.victimOf - print + # Mark fit as calculated self.__calculated = True @@ -492,7 +480,7 @@ class Fit(object): self.register(item) item.calculateModifiedAttributes(self, runTime, False) if projected is True: - #for _ in xrange(projectionInfo.amount): + for _ in xrange(projectionInfo.amount): targetFit.register(item) item.calculateModifiedAttributes(targetFit, runTime, True) @@ -501,7 +489,7 @@ class Fit(object): # Only apply projected fits if fit it not projected itself. if not projected: for fit in self.projectedFits: - #if fit.getProjectionInfo(self.ID).active: + if fit.getProjectionInfo(self.ID).active: fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage) timer.checkpoint('Done with fit calculation') diff --git a/service/fit.py b/service/fit.py index 7b4f6e406..be7efa4c8 100644 --- a/service/fit.py +++ b/service/fit.py @@ -175,7 +175,7 @@ class Fit(object): fit = eos.db.getFit(fitID) sFleet = Fleet.getInstance() sFleet.removeAssociatedFleetData(fit) - self.removeProjectedData(fitID) + #self.removeProjectedData(fitID) eos.db.remove(fit) @@ -247,6 +247,7 @@ class Fit(object): fit.fleet = f if not projected: + print "Not projected, getting projected fits" for fitP in fit.projectedFits: self.getFit(fitP.ID, projected = True) self.recalc(fit, withBoosters=True) @@ -326,9 +327,12 @@ class Fit(object): if thing.ID == fitID: return + if thing in fit.projectedFits: + return + fit.__projectedFits[thing.ID] = thing - # this bit is required -- see GH issue + # this bit is required -- see GH issue # 83 eos.db.saveddata_session.flush() eos.db.saveddata_session.refresh(thing) elif thing.category.name == "Drone": @@ -369,9 +373,11 @@ class Fit(object): thing.state = self.__getProposedState(thing, click) if not thing.canHaveState(thing.state, fit): thing.state = State.OFFLINE - elif isinstance(thing, eos.types.Fit) and thing.projectionInfo is not None: + elif isinstance(thing, eos.types.Fit): print "toggle fit" - thing.projectionInfo.active = not thing.projectionInfo.active + projectionInfo = thing.getProjectionInfo(fitID) + if projectionInfo: + projectionInfo.active = not projectionInfo.active eos.db.commit() self.recalc(fit) From 4216904736885ddebf335d71cfc29c5bfaa68931 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 15:46:42 -0400 Subject: [PATCH 11/34] Remove function to remove projected fits correctly. This is now handled by proper DB relationships --- service/fit.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/service/fit.py b/service/fit.py index be7efa4c8..ebb7024a8 100644 --- a/service/fit.py +++ b/service/fit.py @@ -175,7 +175,6 @@ class Fit(object): fit = eos.db.getFit(fitID) sFleet = Fleet.getInstance() sFleet.removeAssociatedFleetData(fit) - #self.removeProjectedData(fitID) eos.db.remove(fit) @@ -193,14 +192,6 @@ class Fit(object): fit.clear() return fit - def removeProjectedData(self, fitID): - """Removes projection relation from ships that have fitID as projection. See GitHub issue #90""" - fit = eos.db.getFit(fitID) - fits = eos.db.getProjectedFits(fitID) - - for projectee in fits: - projectee.projectedFits.remove(fit) - def toggleFactorReload(self, fitID): if fitID is None: return None From 609ee13cd696010758a2328e2f52987cf25ffd0b Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 15:58:45 -0400 Subject: [PATCH 12/34] Redirect stderr and stdout to logger when we are frozen. Need to test this. --- config.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/config.py b/config.py index a33ec451a..fb0e93eff 100644 --- a/config.py +++ b/config.py @@ -32,6 +32,21 @@ staticPath = None saveDB = None gameDB = None +class StreamToLogger(object): + """ + Fake file-like stream object that redirects writes to a logger instance. + From: http://www.electricmonk.nl/log/2011/08/14/redirect-stdout-and-stderr-to-a-logger-in-python/ + """ + def __init__(self, logger, log_level=logging.INFO): + self.logger = logger + self.log_level = log_level + self.linebuf = '' + + def write(self, buf): + for line in buf.rstrip().splitlines(): + self.logger.log(self.log_level, line.rstrip()) + + def defPaths(): global pyfaPath global savePath @@ -66,19 +81,14 @@ def defPaths(): logging.info("Starting pyfa") - # Redirect stderr to file if we're requested to do so - stderrToFile = getattr(configforced, "stderrToFile", None) - if stderrToFile is True: - if not os.path.exists(savePath): - os.mkdir(savePath) - sys.stderr = open(os.path.join(savePath, "error_log.txt"), "w") + if hasattr(sys, 'frozen'): + stdout_logger = logging.getLogger('STDOUT') + sl = StreamToLogger(stdout_logger, logging.INFO) + sys.stdout = sl - # Same for stdout - stdoutToFile = getattr(configforced, "stdoutToFile", None) - if stdoutToFile is True: - if not os.path.exists(savePath): - os.mkdir(savePath) - sys.stdout = open(os.path.join(savePath, "output_log.txt"), "w") + stderr_logger = logging.getLogger('STDERR') + sl = StreamToLogger(stderr_logger, logging.ERROR) + sys.stderr = sl # Static EVE Data from the staticdata repository, should be in the staticdata # directory in our pyfa directory From 2256efacb0baafe2d681c24b3038f15fdca4a22e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 16:40:00 -0400 Subject: [PATCH 13/34] Do migration stuff for projected fits --- eos/db/migrations/upgrade10.py | 16 ++++++++++++++++ eos/db/saveddata/fit.py | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 eos/db/migrations/upgrade10.py diff --git a/eos/db/migrations/upgrade10.py b/eos/db/migrations/upgrade10.py new file mode 100644 index 000000000..0bfb0f0ee --- /dev/null +++ b/eos/db/migrations/upgrade10.py @@ -0,0 +1,16 @@ +""" +Migration 10 + +- Adds active attribute to projected fits +""" + +import sqlalchemy + +def upgrade(saveddata_engine): + # Update projectedFits schema to include active attribute + try: + saveddata_engine.execute("SELECT active FROM projectedFits LIMIT 1") + except sqlalchemy.exc.DatabaseError: + saveddata_engine.execute("ALTER TABLE projectedFits ADD COLUMN active BOOLEAN") + saveddata_engine.execute("UPDATE projectedFits SET active = 1") + saveddata_engine.execute("UPDATE projectedFits SET amount = 1") diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 744a849a0..24773a429 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -47,8 +47,8 @@ fits_table = Table("fits", saveddata_meta, projectedFits_table = Table("projectedFits", saveddata_meta, Column("sourceID", ForeignKey("fits.ID"), primary_key = True), Column("victimID", ForeignKey("fits.ID"), primary_key = True), - Column("amount", Integer), - Column("active", Boolean), + Column("amount", Integer, nullable = False, default = 1), + Column("active", Boolean, nullable = False, default = 1), ) class ProjectedFit(object): From 23b458534f5d867730ded5ea24a3289fd6b980d6 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 10 Jul 2015 23:22:58 -0400 Subject: [PATCH 14/34] Remove unneeded collection class for projected fits --- eos/effectHandlerHelpers.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index 40349f467..3bf576a4c 100644 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -232,13 +232,6 @@ class HandledProjectedDroneList(HandledDroneCargoList): if proj.isInvalid or not proj.item.isType("projected"): self.remove(proj) -# @todo: remove this once we are sure we no longer need it -class HandledProjectedFitList(HandledList): - def append(self, proj): - print "apppending projected fit", proj - proj.projected = True - list.append(self, proj) - class HandledItem(object): def preAssignItemAttr(self, *args, **kwargs): self.itemModifiedAttributes.preAssign(*args, **kwargs) From 86ee5292d887e21164203b550b5783f9575b3b0c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 11 Jul 2015 11:19:10 -0400 Subject: [PATCH 15/34] Fix fit copying and deleting fits not being reflected in other fits. --- eos/saveddata/fit.py | 8 +++++--- service/fit.py | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 3048b1702..a58946009 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -210,11 +210,10 @@ class Fit(object): @property def projectedFits(self): # only in extreme edge cases will the fit be invalid, but to be sure do - # not return them. By this time + # not return them. return [fit for fit in self.__projectedFits.values() if not fit.isInvalid] def getProjectionInfo(self, fitID): - print "get projection info for fitID: ", fitID return self.projectedOnto.get(fitID, None) @property @@ -967,6 +966,9 @@ class Fit(object): c.append(deepcopy(i, memo)) for fit in self.projectedFits: - copy.projectedFits.append(fit) + copy.__projectedFits[fit.ID] = fit + # this bit is required -- see GH issue # 83 + eos.db.saveddata_session.flush() + eos.db.saveddata_session.refresh(fit) return copy diff --git a/service/fit.py b/service/fit.py index ebb7024a8..40cbe8eb0 100644 --- a/service/fit.py +++ b/service/fit.py @@ -178,6 +178,11 @@ class Fit(object): eos.db.remove(fit) + # refresh any fits this fit is projected onto. Otherwise, if we have + # already loaded those fits, they will not reflect the changes + for projection in fit.projectedOnto.values(): + eos.db.saveddata_session.refresh(projection.victim_fit) + def copyFit(self, fitID): fit = eos.db.getFit(fitID) newFit = copy.deepcopy(fit) From 63fce4be1785f3bef43391f3a0bdbb1b4e0721de Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 11 Jul 2015 16:27:02 -0400 Subject: [PATCH 16/34] Handle self projections by creating a copy of the fit. Due to the way effects are calculated, we would have double effects implemented if not for the copy --- eos/saveddata/fit.py | 16 +++++++++++++++- service/fit.py | 3 --- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index a58946009..b7f7a4f2f 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -29,6 +29,7 @@ from eos.saveddata.module import State from eos.saveddata.mode import Mode import eos.db import time +import copy from utils.timer import Timer import logging @@ -414,14 +415,23 @@ class Fit(object): pass def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): - timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger) + timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger) logger.debug("Starting fit calculation on: %d %s (%s)" % (self.ID, self.name, self.ship.item.name)) + shadow = False if targetFit: logger.debug("Applying projections to target: %d %s (%s)", targetFit.ID, targetFit.name, targetFit.ship.item.name) projectionInfo = self.getProjectionInfo(targetFit.ID) logger.debug("ProjectionInfo: %s", ', '.join("%s: %s" % item for item in vars(projectionInfo).items())) + if self == targetFit: + shadow = True + self = copy.deepcopy(self) + logger.debug("Handling self projection - making shadow copy of fit. %s => %s", projectionInfo.source_fit, self) + # we rollback because when we copy a fit, flush() is called to + # properly handle projection updates. However, we do not want to + # save this fit to the database, so we can immediately rollback + eos.db.saveddata_session.rollback() refreshBoosts = False if withBoosters is True: @@ -495,6 +505,10 @@ class Fit(object): timer.checkpoint('Done with fit calculation') + if shadow: + logger.debug("Delete shadow fit object") + del self + def fill(self): """ Fill this fit's module slots with enough dummy slots so that all slots are used. diff --git a/service/fit.py b/service/fit.py index 40cbe8eb0..ac641a2bd 100644 --- a/service/fit.py +++ b/service/fit.py @@ -320,9 +320,6 @@ class Fit(object): eager=("attributes", "group.category")) if isinstance(thing, eos.types.Fit): - if thing.ID == fitID: - return - if thing in fit.projectedFits: return From c571fdc5e6429e34251ebb759ee1df37276d359e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Mon, 13 Jul 2015 19:29:23 -0400 Subject: [PATCH 17/34] Fit fit alterations with self projections --- eos/saveddata/fit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index b7f7a4f2f..e22360de6 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -428,10 +428,10 @@ class Fit(object): shadow = True self = copy.deepcopy(self) logger.debug("Handling self projection - making shadow copy of fit. %s => %s", projectionInfo.source_fit, self) - # we rollback because when we copy a fit, flush() is called to - # properly handle projection updates. However, we do not want to - # save this fit to the database, so we can immediately rollback - eos.db.saveddata_session.rollback() + # we delete the fit because when we copy a fit, flush() is + # called to properly handle projection updates. However, we do + # not want to save this fit to the database, so simply remove it + eos.db.saveddata_session.delete(self) refreshBoosts = False if withBoosters is True: From f591ecba103029fa3a9ca1b612caf427f5a3ad86 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 14 Jul 2015 16:15:52 -0400 Subject: [PATCH 18/34] Fix use case where gang boosts were not being applied when projections were added/removed. --- service/fit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/fit.py b/service/fit.py index ac641a2bd..2ac93e4ee 100644 --- a/service/fit.py +++ b/service/fit.py @@ -941,7 +941,7 @@ class Fit(object): eos.db.commit() self.recalc(fit) - def recalc(self, fit, withBoosters=False): + def recalc(self, fit, withBoosters=True): logger.debug("="*10+"recalc"+"="*10) if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] From 9a1b0f07c0c5c2dfa13811a4b6bc523c57d604fb Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 14 Jul 2015 16:32:10 -0400 Subject: [PATCH 19/34] Added documentation on why projections don't respect the __calculated flag which gang boosts do --- eos/saveddata/fit.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index e22360de6..decf8e51d 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -460,6 +460,18 @@ class Fit(object): projected = True # If fit is calculated and we have nothing to do here, get out + + # A note on why projected fits don't get to return here. If we return + # here, the projection afflictions will not be run as they are + # intertwined into the regular fit calculations. So, even if the fit has + # been calculated, we need to recalculate it again just to apply the + # projections. This is in contract to gang boosts, which are only + # calculated once, and their items are then looped and accessed with + # self.gangBoosts.iteritems() + # We might be able to exit early in the fit calculations if we separate + # projections from the normal fit calculations. But we must ensure that + # projection have modifying stuff applied, such as gang boosts and other + # local modules that may help if self.__calculated and not projected: logger.debug("Fit has already been calculated and is not projected, returning") return From 3bed268d81fe5a950f4dd97ab052e91c5d0a27b9 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Tue, 14 Jul 2015 19:13:56 -0400 Subject: [PATCH 20/34] Fix use case for downgrading and adding a row with NULL --- eos/db/saveddata/fit.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 24773a429..08cbef344 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -55,8 +55,8 @@ class ProjectedFit(object): def __init__(self, sourceID, source_fit, amount=1, active=True): self.sourceID = sourceID self.source_fit = source_fit - self.amount = amount self.active = active + self.__amount = amount @reconstructor def init(self): @@ -66,6 +66,16 @@ class ProjectedFit(object): eos.db.saveddata_session.flush() eos.db.saveddata_session.refresh(self.victim_fit) + # We have a series of setters and getters here just in case someone + # downgrades and screws up the table with NULL values + @property + def amount(self): + return self.__amount or 1 + + @amount.setter + def amount(self, amount): + self.__amount = amount + Fit._Fit__projectedFits = association_proxy( "victimOf", # look at the victimOf association... "source_fit", # .. and return the source fits @@ -143,4 +153,8 @@ mapper(Fit, fits_table, } ) -mapper(ProjectedFit, projectedFits_table) +mapper(ProjectedFit, projectedFits_table, + properties = { + "_ProjectedFit__amount": projectedFits_table.c.amount, + } +) From 3ad5aaac89214dc5078188c0c95e5d423c3bf332 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 15 Jul 2015 16:52:09 -0400 Subject: [PATCH 21/34] Fix #331 - gang boosts not applied to self projection --- eos/db/saveddata/fit.py | 5 +++++ eos/saveddata/fit.py | 24 ++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 08cbef344..a40d8c7fb 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -76,6 +76,11 @@ class ProjectedFit(object): def amount(self, amount): self.__amount = amount + def __repr__(self): + return "ProjectedFit(sourceID={}, victimID={}, amount={}, active={}) at {}".format( + self.sourceID, self.victimID, self.amount, self.active, hex(id(self)) + ) + Fit._Fit__projectedFits = association_proxy( "victimOf", # look at the victimOf association... "source_fit", # .. and return the source fits diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index decf8e51d..b8a530752 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -392,6 +392,7 @@ class Fit(object): return self.__modifier def __calculateGangBoosts(self, runTime): + logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, self) for name, info in self.gangBoosts.iteritems(): # Unpack all data required to run effect properly effect, thing = info[1] @@ -416,18 +417,19 @@ class Fit(object): def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger) - logger.debug("Starting fit calculation on: %d %s (%s)" % - (self.ID, self.name, self.ship.item.name)) + logger.debug("Starting fit calculation on: %s", repr(self)) + shadow = False if targetFit: - logger.debug("Applying projections to target: %d %s (%s)", - targetFit.ID, targetFit.name, targetFit.ship.item.name) + logger.debug("Applying projections to target: %s", repr(targetFit)) projectionInfo = self.getProjectionInfo(targetFit.ID) - logger.debug("ProjectionInfo: %s", ', '.join("%s: %s" % item for item in vars(projectionInfo).items())) + logger.debug("ProjectionInfo: %s", projectionInfo) if self == targetFit: + copied = self # original fit shadow = True self = copy.deepcopy(self) - logger.debug("Handling self projection - making shadow copy of fit. %s => %s", projectionInfo.source_fit, self) + self.fleet = copied.fleet + logger.debug("Handling self projection - making shadow copy of fit. %s => %s", repr(copied), repr(self)) # we delete the fit because when we copy a fit, flush() is # called to properly handle projection updates. However, we do # not want to save this fit to the database, so simply remove it @@ -998,3 +1000,13 @@ class Fit(object): eos.db.saveddata_session.refresh(fit) return copy + + def __repr__(self): + return "Fit(ID={}, ship={}, name={}) at {}".format( + self.ID, self.ship.item.name, self.name, hex(id(self)) + ) + + def __str__(self): + return "{} ({})".format( + self.name, self.ship.item.name + ) \ No newline at end of file From d6199a58c2bf9982a839746f5aee82485a8e7728 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 16 Jul 2015 23:59:37 -0400 Subject: [PATCH 22/34] Separate projected fits from list of affectors. Also, show when affected module is projected. Still need to clean up affector tree stuff --- eos/modifiedAttributeDict.py | 9 ++++--- eos/saveddata/character.py | 5 ++++ eos/saveddata/fit.py | 8 ++++-- eos/saveddata/module.py | 8 ++++++ gui/itemStats.py | 47 ++++++++++++++++++++++++------------ 5 files changed, 57 insertions(+), 20 deletions(-) diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 79d871ce5..09497be2a 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -217,13 +217,16 @@ class ModifiedAttributeDict(collections.MutableMapping): if attributeName not in self.__affectedBy: self.__affectedBy[attributeName] = {} affs = self.__affectedBy[attributeName] + origin = self.fit.getOrigin() + fit = origin if origin and origin != self.fit else self.fit # If there's no set for current fit in dictionary, create it - if self.fit not in affs: - affs[self.fit] = [] + if fit not in affs: + affs[fit] = [] # Reassign alias to list - affs = affs[self.fit] + affs = affs[fit] # Get modifier which helps to compose 'Affected by' map modifier = self.fit.getModifier() + # Add current affliction to list affs.append((modifier, operation, bonus, used)) diff --git a/eos/saveddata/character.py b/eos/saveddata/character.py index f0b4d9a56..c3e476526 100644 --- a/eos/saveddata/character.py +++ b/eos/saveddata/character.py @@ -285,5 +285,10 @@ class Skill(HandledItem): copy = Skill(self.item, self.level, self.__ro) return copy + def __repr__(self): + return "Skill(ID={}, name={}) at {}".format( + self.item.ID, self.item.name, hex(id(self)) + ) + class ReadOnlyException(Exception): pass diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index b8a530752..6e1515c44 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -381,8 +381,9 @@ class Fit(object): #Methods to register and get the thing currently affecting the fit, #so we can correctly map "Affected By" - def register(self, currModifier): + def register(self, currModifier, origin=None): self.__modifier = currModifier + self.__origin = origin if hasattr(currModifier, "itemModifiedAttributes"): currModifier.itemModifiedAttributes.fit = self if hasattr(currModifier, "chargeModifiedAttributes"): @@ -391,6 +392,9 @@ class Fit(object): def getModifier(self): return self.__modifier + def getOrigin(self): + return self.__origin + def __calculateGangBoosts(self, runTime): logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, self) for name, info in self.gangBoosts.iteritems(): @@ -506,7 +510,7 @@ class Fit(object): item.calculateModifiedAttributes(self, runTime, False) if projected is True: for _ in xrange(projectionInfo.amount): - targetFit.register(item) + targetFit.register(item, origin=self) item.calculateModifiedAttributes(targetFit, runTime, True) timer.checkpoint('Done with runtime: %s'%runTime) diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index a4fdbec07..32a28c28e 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -639,6 +639,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): copy.state = self.state return copy + def __repr__(self): + if self.item: + return "Module(ID={}, name={}) at {}".format( + self.item.ID, self.item.name, hex(id(self)) + ) + else: + return "EmptyModule() at {}".format(hex(id(self))) + class Rack(Module): ''' This is simply the Module class named something else to differentiate diff --git a/gui/itemStats.py b/gui/itemStats.py index a26406af5..b12eddf4e 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -24,7 +24,7 @@ import bitmapLoader import sys import wx.lib.mixins.listctrl as listmix import wx.html -from eos.types import Ship, Module, Skill, Booster, Implant, Drone, Mode +from eos.types import Fit, Ship, Module, Skill, Booster, Implant, Drone, Mode from gui.utils.numberFormatter import formatAmount import service import config @@ -549,9 +549,9 @@ class ItemEffects (wx.Panel): class ItemAffectedBy (wx.Panel): - ORDER = [Ship, Mode, Module, Drone, Implant, Booster, Skill] + ORDER = [Fit, Ship, Mode, Module, Drone, Implant, Booster, Skill] def __init__(self, parent, stuff, item): - wx.Panel.__init__ (self, parent) + wx.Panel.__init__(self, parent) self.stuff = stuff self.item = item @@ -640,26 +640,37 @@ class ItemAffectedBy (wx.Panel): def PopulateTree(self): root = self.affectedBy.AddRoot("WINPWNZ0R") self.affectedBy.SetPyData(root, None) - + activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() + print activeFit self.imageList = wx.ImageList(16, 16) self.affectedBy.SetImageList(self.imageList) cont = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes things = {} + holding = {} for attrName in cont.iterAfflictions(): # if value is 0 or there has been no change from original to modified, return if cont[attrName] == (cont.getOriginal(attrName) or 0): continue + for fit, afflictors in cont.getAfflictions(attrName).iteritems(): for afflictor, modifier, amount, used in afflictors: + container = things + #print "\t", afflictor, modifier, amount, used, + if not used or afflictor.item is None: continue - if afflictor.item.name not in things: - things[afflictor.item.name] = [type(afflictor), set(), []] + if fit.ID != activeFit: + if fit not in holding: + holding[fit] = {} + container = holding[fit] - info = things[afflictor.item.name] + if afflictor.item.name not in container: + container[afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] + + info = container[afflictor.item.name] info[1].add(afflictor) # If info[1] > 1, there are two separate modules working. # Check to make sure we only include the modifier once @@ -668,15 +679,20 @@ class ItemAffectedBy (wx.Panel): continue info[2].append((attrName, modifier, amount)) + for fit, items in holding.iteritems(): + child = self.affectedBy.AppendItem(root, fit.name, self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))) + self.test(child, holding[fit]) + self.test(root, things) + self.ExpandCollapseTree() + + def test(self, parent, things): order = things.keys() order.sort(key=lambda x: (self.ORDER.index(things[x][0]), x)) - for itemName in order: info = things[itemName] - afflictorType, afflictors, attrData = info + afflictorType, afflictors, attrData, projected = info counter = len(afflictors) - baseAfflictor = afflictors.pop() if afflictorType == Ship: itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) @@ -686,7 +702,12 @@ class ItemAffectedBy (wx.Panel): else: itemIcon = -1 - child = self.affectedBy.AppendItem(root, "%s" % itemName if counter == 1 else "%s x %d" % (itemName,counter), itemIcon) + displayStr = "%s" % itemName if counter == 1 else "%s x %d" % (itemName,counter) + + if projected: + displayStr += " (projected)" + + child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) if counter > 0: attributes = [] @@ -716,7 +737,6 @@ class ItemAffectedBy (wx.Panel): attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon)) attrSorted = sorted(attributes, key = lambda attribName: attribName[0]) - for attr in attrSorted: attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr if self.toggleView == 1: @@ -725,6 +745,3 @@ class ItemAffectedBy (wx.Panel): else: treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized), attrIcon) self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)) - - self.ExpandCollapseTree() - From 71b258a8f545f055dcd0e11479c1fe2937c66767 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 17 Jul 2015 14:59:19 -0400 Subject: [PATCH 23/34] Merge fit attributes with ship --- eos/saveddata/fit.py | 11 +---------- eos/saveddata/ship.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 6e1515c44..4f9f3e867 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -43,14 +43,6 @@ except ImportError: class Fit(object): """Represents a fitting, with modules, ship, implants, etc.""" - EXTRA_ATTRIBUTES = {"armorRepair": 0, - "hullRepair": 0, - "shieldRepair": 0, - "maxActiveDrones": 0, - "maxTargetsLockedFromSkills": 2, - "droneControlRange": 20000, - "cloaked": False, - "siege": False} PEAK_RECHARGE = 0.25 @@ -127,8 +119,7 @@ class Fit(object): self.boostsFits = set() self.gangBoosts = None self.ecmProjectedStr = 1 - self.extraAttributes = ModifiedAttributeDict(self) - self.extraAttributes.original = self.EXTRA_ATTRIBUTES + self.extraAttributes = self.ship.itemModifiedAttributes @property def targetResists(self): diff --git a/eos/saveddata/ship.py b/eos/saveddata/ship.py index 86123bfc1..901aa2607 100644 --- a/eos/saveddata/ship.py +++ b/eos/saveddata/ship.py @@ -26,6 +26,17 @@ import logging logger = logging.getLogger(__name__) class Ship(ItemAttrShortcut, HandledItem): + EXTRA_ATTRIBUTES = { + "armorRepair": 0, + "hullRepair": 0, + "shieldRepair": 0, + "maxActiveDrones": 0, + "maxTargetsLockedFromSkills": 2, + "droneControlRange": 20000, + "cloaked": False, + "siege": False + } + def __init__(self, item): if item.category.name != "Ship": @@ -34,7 +45,8 @@ class Ship(ItemAttrShortcut, HandledItem): self.__item = item self.__modeItems = self.__getModeItems() self.__itemModifiedAttributes = ModifiedAttributeDict() - self.__itemModifiedAttributes.original = self.item.attributes + self.__itemModifiedAttributes.original = dict(self.item.attributes) + self.__itemModifiedAttributes.original.update(self.EXTRA_ATTRIBUTES) self.commandBonus = 0 From 40aeb1ed4aca808468ed7ebea33cbce750f7678f Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 17 Jul 2015 16:33:07 -0400 Subject: [PATCH 24/34] Move active fir to init, fixes bug when refreshing with another active fit --- gui/itemStats.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index b12eddf4e..0fd2b4e72 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -555,6 +555,8 @@ class ItemAffectedBy (wx.Panel): self.stuff = stuff self.item = item + self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() + self.toggleView = 1 self.expand = -1 @@ -640,8 +642,7 @@ class ItemAffectedBy (wx.Panel): def PopulateTree(self): root = self.affectedBy.AddRoot("WINPWNZ0R") self.affectedBy.SetPyData(root, None) - activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() - print activeFit + self.imageList = wx.ImageList(16, 16) self.affectedBy.SetImageList(self.imageList) @@ -662,7 +663,7 @@ class ItemAffectedBy (wx.Panel): if not used or afflictor.item is None: continue - if fit.ID != activeFit: + if fit.ID != self.activeFit: if fit not in holding: holding[fit] = {} container = holding[fit] From d18482072831592f4b92674d920bfe20c8470247 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Fri, 17 Jul 2015 18:28:31 -0400 Subject: [PATCH 25/34] Fix toggle attribute names. Previous way assumed no other trees apart from first child and siblings. Not true now with projected fit trees. Instead of attempting to walk the tree, we simply store the items in a list that we later iterate over. Much easier --- gui/itemStats.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index 0fd2b4e72..02a5e626b 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -560,6 +560,8 @@ class ItemAffectedBy (wx.Panel): self.toggleView = 1 self.expand = -1 + self.treeItems = [] + mainSizer = wx.BoxSizer(wx.VERTICAL) self.affectedBy = wx.TreeCtrl(self, style = wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) @@ -609,18 +611,11 @@ class ItemAffectedBy (wx.Panel): def ToggleViewTree(self): self.Freeze() - root = self.affectedBy.GetRootItem() - child,cookie = self.affectedBy.GetFirstChild(root) - while child.IsOk(): - item,childcookie = self.affectedBy.GetFirstChild(child) - while item.IsOk(): - change = self.affectedBy.GetPyData(item) - display = self.affectedBy.GetItemText(item) - self.affectedBy.SetItemText(item,change) - self.affectedBy.SetPyData(item,display) - item,childcookie = self.affectedBy.GetNextChild(child,childcookie) - - child,cookie = self.affectedBy.GetNextChild(root,cookie) + for item in self.treeItems: + change = self.affectedBy.GetPyData(item) + display = self.affectedBy.GetItemText(item) + self.affectedBy.SetItemText(item,change) + self.affectedBy.SetPyData(item,display) self.Thaw() @@ -746,3 +741,4 @@ class ItemAffectedBy (wx.Panel): else: treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized), attrIcon) self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)) + self.treeItems.append(treeitem) From 2f8c201ab37556f54a39534891fe72ef15d352da Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 18 Jul 2015 14:45:26 -0400 Subject: [PATCH 26/34] Add attribute view --- gui/itemStats.py | 336 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 264 insertions(+), 72 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index 02a5e626b..4701cbe90 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -557,7 +557,8 @@ class ItemAffectedBy (wx.Panel): self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() - self.toggleView = 1 + self.showRealNames = False + self.showAttrView = True self.expand = -1 self.treeItems = [] @@ -575,7 +576,10 @@ class ItemAffectedBy (wx.Panel): self.toggleExpandBtn = wx.ToggleButton( self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer.Add( self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.toggleNameBtn = wx.ToggleButton( self, wx.ID_ANY, u"Real Attribute Names", wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer.Add( self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer.Add( self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) if stuff is not None: @@ -583,8 +587,9 @@ class ItemAffectedBy (wx.Panel): bSizer.Add( self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.refreshBtn.Bind( wx.EVT_BUTTON, self.RefreshTree ) - self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode) + self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleNameMode) self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleExpand) + self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON,self.ToggleViewMode) mainSizer.Add( bSizer, 0, wx.ALIGN_RIGHT) self.SetSizer(mainSizer) @@ -614,8 +619,8 @@ class ItemAffectedBy (wx.Panel): for item in self.treeItems: change = self.affectedBy.GetPyData(item) display = self.affectedBy.GetItemText(item) - self.affectedBy.SetItemText(item,change) - self.affectedBy.SetPyData(item,display) + self.affectedBy.SetItemText(item, change) + self.affectedBy.SetPyData(item, display) self.Thaw() @@ -630,43 +635,84 @@ class ItemAffectedBy (wx.Panel): event.Skip() def ToggleViewMode(self, event): - self.toggleView *=-1 + self.showAttrView = not self.showAttrView + self.affectedBy.DeleteAllItems() + self.PopulateTree() + event.Skip() + + def ToggleNameMode(self, event): + self.showRealNames = not self.showRealNames self.ToggleViewTree() event.Skip() def PopulateTree(self): + # sheri was here + del self.treeItems[:] root = self.affectedBy.AddRoot("WINPWNZ0R") self.affectedBy.SetPyData(root, None) self.imageList = wx.ImageList(16, 16) self.affectedBy.SetImageList(self.imageList) - cont = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes - things = {} - holding = {} + if self.showAttrView: + self.buildAttributeView(root) + else: + self.buildModuleView(root) - for attrName in cont.iterAfflictions(): + self.ExpandCollapseTree() + def sortAttrDisplayName(self, attr): + info = self.stuff.item.attributes.get(attr) + if info and info.displayName != "": + return info.displayName + + return attr + def buildAttributeView(self, root): + # We first build a usable dictionary of items. The key is either a fit + # if the afflictions stem from a projected fit, or self.stuff if they + # are local afflictions (everything else, even gang boosts at this time) + # The value of this is yet another dictionary in the following format: + # + # "atribute name": { + # "Module Name": [ + # class of affliction, + # set of afflictors (such as 2 of the same module), + # info on affliction (attribute name, modifier, and modification amount), + # whether this affliction is actually used (unlearned skills are not used) + # ] + # } + + attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes + container = {} + for attrName in attributes.iterAfflictions(): # if value is 0 or there has been no change from original to modified, return - if cont[attrName] == (cont.getOriginal(attrName) or 0): + if attributes[attrName] == (attributes.getOriginal(attrName) or 0): continue - for fit, afflictors in cont.getAfflictions(attrName).iteritems(): + for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): for afflictor, modifier, amount, used in afflictors: - container = things - #print "\t", afflictor, modifier, amount, used, if not used or afflictor.item is None: continue if fit.ID != self.activeFit: - if fit not in holding: - holding[fit] = {} - container = holding[fit] + # affliction fit does not match our fit + if fit not in container: + container[fit] = {} + items = container[fit] + else: + # local afflictions + if self.stuff not in container: + container[self.stuff] = {} + items = container[self.stuff] - if afflictor.item.name not in container: - container[afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] + # items hold our module: info mappings + if attrName not in items: + items[attrName] = {} - info = container[afflictor.item.name] + if afflictor.item.name not in items[attrName]: + items[attrName][afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] + + info = items[attrName][afflictor.item.name] info[1].add(afflictor) # If info[1] > 1, there are two separate modules working. # Check to make sure we only include the modifier once @@ -675,54 +721,74 @@ class ItemAffectedBy (wx.Panel): continue info[2].append((attrName, modifier, amount)) - for fit, items in holding.iteritems(): - child = self.affectedBy.AppendItem(root, fit.name, self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons"))) - self.test(child, holding[fit]) - self.test(root, things) - self.ExpandCollapseTree() + # Make sure projected fits are on top + rootOrder = container.keys() + rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) - def test(self, parent, things): - order = things.keys() - order.sort(key=lambda x: (self.ORDER.index(things[x][0]), x)) - for itemName in order: - info = things[itemName] + # Now, we take our created dictionary and start adding stuff to our tree + for thing in rootOrder: + # This block simply directs which parent we are adding to (root or projected fit) + if thing == self.stuff: + parent = root + else: # projected fit + icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) + child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) + parent = child - afflictorType, afflictors, attrData, projected = info - counter = len(afflictors) - baseAfflictor = afflictors.pop() - if afflictorType == Ship: - itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) - elif baseAfflictor.item.icon: - bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack") - itemIcon = self.imageList.Add(bitmap) if bitmap else -1 - else: - itemIcon = -1 + attributes = container[thing] + attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName) - displayStr = "%s" % itemName if counter == 1 else "%s x %d" % (itemName,counter) + for attrName in attrOrder: + attrInfo = self.stuff.item.attributes.get(attrName) + displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName - if projected: - displayStr += " (projected)" - - child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) - - if counter > 0: - attributes = [] - for attrName, attrModifier, attrAmount in attrData: - attrInfo = self.stuff.item.attributes.get(attrName) - displayName = attrInfo.displayName if attrInfo else "" - - if attrInfo: - if attrInfo.icon is not None: - iconFile = attrInfo.icon.iconFile - icon = bitmapLoader.getBitmap(iconFile, "pack") - if icon is None: - icon = bitmapLoader.getBitmap("transparent16x16", "icons") - - attrIcon = self.imageList.Add(icon) - else: - attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) + if attrInfo: + if attrInfo.icon is not None: + iconFile = attrInfo.icon.iconFile + icon = bitmapLoader.getBitmap(iconFile, "pack") + if icon is None: + icon = bitmapLoader.getBitmap("transparent16x16", "icons") + attrIcon = self.imageList.Add(icon) else: attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) + else: + attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) + + + if self.showRealNames: + display = attrName + saved = displayName + else: + display = displayName + saved = attrName + + # this is the attribute node + child = self.affectedBy.AppendItem(parent, display, attrIcon) + self.affectedBy.SetPyData(child, saved) + self.treeItems.append(child) + + items = attributes[attrName] + for itemName, info in items.iteritems(): + afflictorType, afflictors, attrData, projected = info + attrName, attrModifier, attrAmount = attrData[0] + counter = len(afflictors) + baseAfflictor = afflictors.pop() + + if afflictorType == Ship: + itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) + elif baseAfflictor.item.icon: + bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack") + itemIcon = self.imageList.Add(bitmap) if bitmap else -1 + else: + itemIcon = -1 + + displayStr = itemName + + if counter > 1: + displayStr += " x {}".format(counter) + + if projected: + displayStr += " (projected)" if attrModifier == "s*": attrModifier = "*" @@ -730,15 +796,141 @@ class ItemAffectedBy (wx.Panel): else: penalized = "" - attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon)) + # this is the Module node, the attribute will be attached to this + display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized) + self.affectedBy.AppendItem(child, display, itemIcon) - attrSorted = sorted(attributes, key = lambda attribName: attribName[0]) - for attr in attrSorted: - attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr - if self.toggleView == 1: - treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized), attrIcon) - self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized)) + def buildModuleView(self, root): + # We first build a usable dictionary of items. The key is either a fit + # if the afflictions stem from a projected fit, or self.stuff if they + # are local afflictions (everything else, even gang boosts at this time) + # The value of this is yet another dictionary in the following format: + # + # "Module Name": [ + # class of affliction, + # set of afflictors (such as 2 of the same module), + # info on affliction (attribute name, modifier, and modification amount), + # whether this affliction is actually used (unlearned skills are not used) + # ] + + attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes + container = {} + for attrName in attributes.iterAfflictions(): + # if value is 0 or there has been no change from original to modified, return + if attributes[attrName] == (attributes.getOriginal(attrName) or 0): + continue + + for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): + for afflictor, modifier, amount, used in afflictors: + + if not used or afflictor.item is None: + continue + + if fit.ID != self.activeFit: + # affliction fit does not match our fit + if fit not in container: + container[fit] = {} + items = container[fit] else: - treeitem = self.affectedBy.AppendItem(child, "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized), attrIcon) - self.affectedBy.SetPyData(treeitem,"%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized)) - self.treeItems.append(treeitem) + # local afflictions + if self.stuff not in container: + container[self.stuff] = {} + items = container[self.stuff] + + # items hold our module: info mappings + if afflictor.item.name not in items: + items[afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] + + info = items[afflictor.item.name] + info[1].add(afflictor) + # If info[1] > 1, there are two separate modules working. + # Check to make sure we only include the modifier once + # See GH issue 154 + if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]: + continue + info[2].append((attrName, modifier, amount)) + + # Make sure projected fits are on top + rootOrder = container.keys() + rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) + + # Now, we take our created dictionary and start adding stuff to our tree + for thing in rootOrder: + # This block simply directs which parent we are adding to (root or projected fit) + if thing == self.stuff: + parent = root + else: # projected fit + icon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) + child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) + parent = child + + items = container[thing] + order = items.keys() + order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x)) + + for itemName in order: + info = items[itemName] + + afflictorType, afflictors, attrData, projected = info + counter = len(afflictors) + baseAfflictor = afflictors.pop() + if afflictorType == Ship: + itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) + elif baseAfflictor.item.icon: + bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack") + itemIcon = self.imageList.Add(bitmap) if bitmap else -1 + else: + itemIcon = -1 + + displayStr = itemName + + if counter > 1: + displayStr += " x {}".format(counter) + + if projected: + displayStr += " (projected)" + + # this is the Module node, the attribute will be attached to this + child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) + + if counter > 0: + attributes = [] + for attrName, attrModifier, attrAmount in attrData: + attrInfo = self.stuff.item.attributes.get(attrName) + displayName = attrInfo.displayName if attrInfo else "" + + if attrInfo: + if attrInfo.icon is not None: + iconFile = attrInfo.icon.iconFile + icon = bitmapLoader.getBitmap(iconFile, "pack") + if icon is None: + icon = bitmapLoader.getBitmap("transparent16x16", "icons") + + attrIcon = self.imageList.Add(icon) + else: + attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) + else: + attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) + + if attrModifier == "s*": + attrModifier = "*" + penalized = "(penalized)" + else: + penalized = "" + + attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon)) + + attrSorted = sorted(attributes, key = lambda attribName: attribName[0]) + for attr in attrSorted: + attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr + + if self.showRealNames: + display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) + saved = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) + else: + display = "%s %s %.2f %s" % ((displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized) + saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) + + treeitem = self.affectedBy.AppendItem(child, display, attrIcon) + self.affectedBy.SetPyData(treeitem, saved) + self.treeItems.append(treeitem) From b06ce24d4ad3e9b54b04b0707ccd3580fa1be6a2 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 18 Jul 2015 15:20:15 -0400 Subject: [PATCH 27/34] Fix attribute view items (two of the same item would merge into one, even if they had different modifiers. now simply list them individually) --- gui/itemStats.py | 49 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index 4701cbe90..408adb96d 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -660,23 +660,24 @@ class ItemAffectedBy (wx.Panel): self.buildModuleView(root) self.ExpandCollapseTree() - def sortAttrDisplayName(self, attr): - info = self.stuff.item.attributes.get(attr) - if info and info.displayName != "": - return info.displayName - return attr + def sortAttrDisplayName(self, attr): + info = self.stuff.item.attributes.get(attr) + if info and info.displayName != "": + return info.displayName + + return attr + def buildAttributeView(self, root): # We first build a usable dictionary of items. The key is either a fit # if the afflictions stem from a projected fit, or self.stuff if they # are local afflictions (everything else, even gang boosts at this time) # The value of this is yet another dictionary in the following format: # - # "atribute name": { + # "attribute name": { # "Module Name": [ # class of affliction, - # set of afflictors (such as 2 of the same module), - # info on affliction (attribute name, modifier, and modification amount), + # afflictor info (afflictor, modifier, and modification amount), # whether this affliction is actually used (unlearned skills are not used) # ] # } @@ -707,19 +708,9 @@ class ItemAffectedBy (wx.Panel): # items hold our module: info mappings if attrName not in items: - items[attrName] = {} + items[attrName] = [] - if afflictor.item.name not in items[attrName]: - items[attrName][afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] - - info = items[attrName][afflictor.item.name] - info[1].add(afflictor) - # If info[1] > 1, there are two separate modules working. - # Check to make sure we only include the modifier once - # See GH issue 154 - if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]: - continue - info[2].append((attrName, modifier, amount)) + items[attrName].append((type(afflictor), afflictor, modifier, amount, getattr(afflictor, "projected", False))) # Make sure projected fits are on top rootOrder = container.keys() @@ -754,7 +745,6 @@ class ItemAffectedBy (wx.Panel): else: attrIcon = self.imageList.Add(bitmapLoader.getBitmap("07_15", "pack")) - if self.showRealNames: display = attrName saved = displayName @@ -768,24 +758,19 @@ class ItemAffectedBy (wx.Panel): self.treeItems.append(child) items = attributes[attrName] - for itemName, info in items.iteritems(): - afflictorType, afflictors, attrData, projected = info - attrName, attrModifier, attrAmount = attrData[0] - counter = len(afflictors) - baseAfflictor = afflictors.pop() + items.sort(key=lambda x: self.ORDER.index(x[0])) + for itemInfo in items: + afflictorType, afflictor, attrModifier, attrAmount, projected = itemInfo if afflictorType == Ship: itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) - elif baseAfflictor.item.icon: - bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack") + elif afflictor.item.icon: + bitmap = bitmapLoader.getBitmap(afflictor.item.icon.iconFile, "pack") itemIcon = self.imageList.Add(bitmap) if bitmap else -1 else: itemIcon = -1 - displayStr = itemName - - if counter > 1: - displayStr += " x {}".format(counter) + displayStr = afflictor.item.name if projected: displayStr += " (projected)" From 9941b6c74b9ecbd426517ac1b37c62b8617c805d Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 18 Jul 2015 15:23:10 -0400 Subject: [PATCH 28/34] Make default view module again --- gui/itemStats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index 408adb96d..3d5190e63 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -558,7 +558,7 @@ class ItemAffectedBy (wx.Panel): self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() self.showRealNames = False - self.showAttrView = True + self.showAttrView = False self.expand = -1 self.treeItems = [] @@ -576,7 +576,7 @@ class ItemAffectedBy (wx.Panel): self.toggleExpandBtn = wx.ToggleButton( self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer.Add( self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.toggleNameBtn = wx.ToggleButton( self, wx.ID_ANY, u"Real Attribute Names", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.toggleNameBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer.Add( self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.toggleViewBtn = wx.ToggleButton( self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0 ) From 4596c526a2c40dfc40673a15684e1a46cc1cc366 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Sat, 18 Jul 2015 23:39:16 -0400 Subject: [PATCH 29/34] Fix #335 - properly represent charge modifiers --- gui/itemStats.py | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/gui/itemStats.py b/gui/itemStats.py index 3d5190e63..d29895e7a 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -677,8 +677,10 @@ class ItemAffectedBy (wx.Panel): # "attribute name": { # "Module Name": [ # class of affliction, - # afflictor info (afflictor, modifier, and modification amount), - # whether this affliction is actually used (unlearned skills are not used) + # affliction item (required due to GH issue #335) + # modifier type + # amount of modification + # whether this affliction was projected # ] # } @@ -710,7 +712,13 @@ class ItemAffectedBy (wx.Panel): if attrName not in items: items[attrName] = [] - items[attrName].append((type(afflictor), afflictor, modifier, amount, getattr(afflictor, "projected", False))) + if afflictor == self.stuff and getattr(afflictor, 'charge', None): + # we are showing a charges modifications, see #335 + item = afflictor.charge + else: + item = afflictor.item + + items[attrName].append((type(afflictor), item, modifier, amount, getattr(afflictor, "projected", False))) # Make sure projected fits are on top rootOrder = container.keys() @@ -760,17 +768,17 @@ class ItemAffectedBy (wx.Panel): items = attributes[attrName] items.sort(key=lambda x: self.ORDER.index(x[0])) for itemInfo in items: - afflictorType, afflictor, attrModifier, attrAmount, projected = itemInfo + afflictorType, item, attrModifier, attrAmount, projected = itemInfo if afflictorType == Ship: itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) - elif afflictor.item.icon: - bitmap = bitmapLoader.getBitmap(afflictor.item.icon.iconFile, "pack") + elif item.icon: + bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack") itemIcon = self.imageList.Add(bitmap) if bitmap else -1 else: itemIcon = -1 - displayStr = afflictor.item.name + displayStr = item.name if projected: displayStr += " (projected)" @@ -795,6 +803,7 @@ class ItemAffectedBy (wx.Panel): # class of affliction, # set of afflictors (such as 2 of the same module), # info on affliction (attribute name, modifier, and modification amount), + # item that will be used to determine icon (required due to GH issue #335) # whether this affliction is actually used (unlearned skills are not used) # ] @@ -822,11 +831,17 @@ class ItemAffectedBy (wx.Panel): container[self.stuff] = {} items = container[self.stuff] - # items hold our module: info mappings - if afflictor.item.name not in items: - items[afflictor.item.name] = [type(afflictor), set(), [], getattr(afflictor, "projected", False)] + if afflictor == self.stuff and getattr(afflictor, 'charge', None): + # we are showing a charges modifications, see #335 + item = afflictor.charge + else: + item = afflictor.item - info = items[afflictor.item.name] + # items hold our module: info mappings + if item.name not in items: + items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)] + + info = items[item.name] info[1].add(afflictor) # If info[1] > 1, there are two separate modules working. # Check to make sure we only include the modifier once @@ -856,13 +871,12 @@ class ItemAffectedBy (wx.Panel): for itemName in order: info = items[itemName] - afflictorType, afflictors, attrData, projected = info + afflictorType, afflictors, attrData, item, projected = info counter = len(afflictors) - baseAfflictor = afflictors.pop() if afflictorType == Ship: itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) - elif baseAfflictor.item.icon: - bitmap = bitmapLoader.getBitmap(baseAfflictor.item.icon.iconFile, "pack") + elif item.icon: + bitmap = bitmapLoader.getBitmap(item.icon.iconFile, "pack") itemIcon = self.imageList.Add(bitmap) if bitmap else -1 else: itemIcon = -1 From 17733d5951c95e39a995e3c89f47eb31c674904e Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 22 Jul 2015 13:00:35 -0400 Subject: [PATCH 30/34] Added missing tactical mode calculations... whoops --- eos/saveddata/fit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 4f9f3e867..f26049e2d 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -487,7 +487,7 @@ class Fit(object): if not projected: # if not a projected fit, add a couple of more things - c = chain(c, self.projectedDrones, self.projectedModules) + c = chain(c, (self.mode,), self.projectedDrones, self.projectedModules) # We calculate gang bonuses first so that projected fits get them if self.gangBoosts is not None: From cd0b0eada071d4939c9752536e1e25f1dc54e57c Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 22 Jul 2015 13:12:57 -0400 Subject: [PATCH 31/34] Fix graphing --- gui/builtinViewColumns/baseName.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index cf9222414..97b992337 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -18,9 +18,7 @@ # along with pyfa. If not, see . #=============================================================================== -from gui import builtinViewColumns from gui.viewColumn import ViewColumn -from gui import bitmapLoader import gui.mainFrame import wx @@ -31,10 +29,12 @@ class BaseName(ViewColumn): name = "Base Name" def __init__(self, fittingView, params): ViewColumn.__init__(self, fittingView) + self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.columnText = "Name" self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "icons") self.mask = wx.LIST_MASK_TEXT + self.projectedView = isinstance(fittingView, gui.projectedView.ProjectedView) def getText(self, stuff): if isinstance(stuff, Drone): @@ -42,8 +42,12 @@ class BaseName(ViewColumn): elif isinstance(stuff, Cargo): return "%dx %s" % (stuff.amount, stuff.item.name) elif isinstance(stuff, Fit): - fitID = self.mainFrame.getActiveFit() - return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name) + if self.projectedView: + # we need a little more information for the projected view + fitID = self.mainFrame.getActiveFit() + return "%dx %s (%s)" % (stuff.getProjectionInfo(fitID).amount, stuff.name, stuff.ship.item.name) + else: + return "%s (%s)" % (stuff.name, stuff.ship.item.name) elif isinstance(stuff, Rack): if service.Fit.getInstance().serviceFittingOptions["rackLabels"]: if stuff.slot == Slot.MODE: From 5a4f526b2baa24ee3e82cdf78032f8a37b8b4b2f Mon Sep 17 00:00:00 2001 From: blitzmann Date: Wed, 22 Jul 2015 14:27:44 -0400 Subject: [PATCH 32/34] Fix fit copying --- eos/saveddata/fit.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index f26049e2d..a669f99ea 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -83,6 +83,10 @@ class Fit(object): try: self.__ship = Ship(item) + # @todo extra attributes is now useless, however it set to be + # the same as ship attributes for ease (so we don't have to + # change all instances in source). Remove this at some point + self.extraAttributes = self.__ship.itemModifiedAttributes except ValueError: logger.error("Item (id: %d) is not a Ship", self.shipID) return @@ -119,7 +123,6 @@ class Fit(object): self.boostsFits = set() self.gangBoosts = None self.ecmProjectedStr = 1 - self.extraAttributes = self.ship.itemModifiedAttributes @property def targetResists(self): @@ -172,8 +175,11 @@ class Fit(object): def ship(self, ship): self.__ship = ship self.shipID = ship.item.ID if ship is not None else None - # set mode of new ship - self.mode = self.ship.validateModeItem(None) if ship is not None else None + if ship is not None: + # set mode of new ship + self.mode = self.ship.validateModeItem(None) if ship is not None else None + # set fit attributes the same as ship + self.extraAttributes = self.ship.itemModifiedAttributes @property def drones(self): From 9de3600d7f3764baa622e428064be3bda27088b6 Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 23 Jul 2015 01:32:48 -0400 Subject: [PATCH 33/34] Fix self-boosting --- eos/saveddata/fit.py | 23 ++++++----------------- service/fit.py | 2 +- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index a669f99ea..d2de8dfc1 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -393,7 +393,7 @@ class Fit(object): return self.__origin def __calculateGangBoosts(self, runTime): - logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, self) + logger.debug("Applying gang boosts in `%s` runtime for %s", runTime, repr(self)) for name, info in self.gangBoosts.iteritems(): # Unpack all data required to run effect properly effect, thing = info[1] @@ -418,7 +418,7 @@ class Fit(object): def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None): timer = Timer('Fit: {}, {}'.format(self.ID, self.name), logger) - logger.debug("Starting fit calculation on: %s", repr(self)) + logger.debug("Starting fit calculation on: %s, withBoosters: %s", repr(self), withBoosters) shadow = False if targetFit: @@ -436,23 +436,12 @@ class Fit(object): # not want to save this fit to the database, so simply remove it eos.db.saveddata_session.delete(self) - refreshBoosts = False - if withBoosters is True: - refreshBoosts = True - if dirtyStorage is not None and self.ID in dirtyStorage: - refreshBoosts = True - if dirtyStorage is not None: - dirtyStorage.update(self.boostsFits) - if self.fleet is not None and refreshBoosts is True: + if self.fleet is not None and withBoosters is True: logger.debug("Fleet is set, gathering gang boosts") - self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage) + self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters) + timer.checkpoint("Done calculating gang boosts for %s"%repr(self)) elif self.fleet is None: self.gangBoosts = None - if dirtyStorage is not None: - try: - dirtyStorage.remove(self.ID) - except KeyError: - pass # If we're not explicitly asked to project fit onto something, # set self as target fit @@ -476,7 +465,7 @@ class Fit(object): # projection have modifying stuff applied, such as gang boosts and other # local modules that may help if self.__calculated and not projected: - logger.debug("Fit has already been calculated and is not projected, returning") + logger.debug("Fit has already been calculated and is not projected, returning: %s", repr(self)) return # Mark fit as calculated diff --git a/service/fit.py b/service/fit.py index 2ac93e4ee..e848b45d7 100644 --- a/service/fit.py +++ b/service/fit.py @@ -946,4 +946,4 @@ class Fit(object): if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] fit.clear() - fit.calculateModifiedAttributes(withBoosters=withBoosters, dirtyStorage=self.dirtyFitIDs) + fit.calculateModifiedAttributes(withBoosters=withBoosters) From 2f246d0897dcb1f13844fa4e0c1e03864f5ab27a Mon Sep 17 00:00:00 2001 From: blitzmann Date: Thu, 23 Jul 2015 15:32:27 -0400 Subject: [PATCH 34/34] Add context menu to affected by list --- gui/builtinContextMenus/itemStats.py | 3 ++- gui/itemStats.py | 36 +++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/gui/builtinContextMenus/itemStats.py b/gui/builtinContextMenus/itemStats.py index 47054edc6..73ba66a56 100644 --- a/gui/builtinContextMenus/itemStats.py +++ b/gui/builtinContextMenus/itemStats.py @@ -15,7 +15,8 @@ class ItemStats(ContextMenu): "cargoItem", "droneItem", "implantItem", "boosterItem", "skillItem", "projectedModule", - "projectedDrone", "projectedCharge") + "projectedDrone", "projectedCharge", + "itemStats") def getText(self, itmContext, selection): return "{0} Stats".format(itmContext if itmContext is not None else "Item") diff --git a/gui/itemStats.py b/gui/itemStats.py index d29895e7a..9d971aaa7 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -28,6 +28,7 @@ from eos.types import Fit, Ship, Module, Skill, Booster, Implant, Drone, Mode from gui.utils.numberFormatter import formatAmount import service import config +from gui.contextMenu import ContextMenu try: from collections import OrderedDict @@ -595,6 +596,28 @@ class ItemAffectedBy (wx.Panel): self.SetSizer(mainSizer) self.PopulateTree() self.Layout() + self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu) + + def scheduleMenu(self, event): + event.Skip() + wx.CallAfter(self.spawnMenu, event.Item) + + def spawnMenu(self, item): + self.affectedBy.SelectItem(item) + + stuff = self.affectedBy.GetPyData(item) + # String is set as data when we are dealing with attributes, not stuff containers + if stuff is None or isinstance(stuff, basestring): + return + contexts = [] + + # Skills are different in that they don't have itemModifiedAttributes, + # which is needed if we send the container to itemStats dialog. So + # instead, we send the item. + type = stuff.__class__.__name__ + contexts.append(("itemStats", type)) + menu = ContextMenu.getMenu(stuff if type != "Skill" else stuff.item, *contexts) + self.PopupMenu(menu) def ExpandCollapseTree(self): @@ -718,7 +741,7 @@ class ItemAffectedBy (wx.Panel): else: item = afflictor.item - items[attrName].append((type(afflictor), item, modifier, amount, getattr(afflictor, "projected", False))) + items[attrName].append((type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) # Make sure projected fits are on top rootOrder = container.keys() @@ -768,7 +791,7 @@ class ItemAffectedBy (wx.Panel): items = attributes[attrName] items.sort(key=lambda x: self.ORDER.index(x[0])) for itemInfo in items: - afflictorType, item, attrModifier, attrAmount, projected = itemInfo + afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo if afflictorType == Ship: itemIcon = self.imageList.Add(bitmapLoader.getBitmap("ship_small", "icons")) @@ -791,7 +814,9 @@ class ItemAffectedBy (wx.Panel): # this is the Module node, the attribute will be attached to this display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized) - self.affectedBy.AppendItem(child, display, itemIcon) + treeItem = self.affectedBy.AppendItem(child, display, itemIcon) + self.affectedBy.SetPyData(treeItem, afflictor) + def buildModuleView(self, root): # We first build a usable dictionary of items. The key is either a fit @@ -816,8 +841,7 @@ class ItemAffectedBy (wx.Panel): for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): for afflictor, modifier, amount, used in afflictors: - - if not used or afflictor.item is None: + if not used or getattr(afflictor, 'item', None) is None: continue if fit.ID != self.activeFit: @@ -870,7 +894,6 @@ class ItemAffectedBy (wx.Panel): for itemName in order: info = items[itemName] - afflictorType, afflictors, attrData, item, projected = info counter = len(afflictors) if afflictorType == Ship: @@ -891,6 +914,7 @@ class ItemAffectedBy (wx.Panel): # this is the Module node, the attribute will be attached to this child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) + self.affectedBy.SetPyData(child, afflictors.pop()) if counter > 0: attributes = []