diff --git a/eos/db/saveddata/booster.py b/eos/db/saveddata/booster.py index e40762cd9..fa29c70b2 100644 --- a/eos/db/saveddata/booster.py +++ b/eos/db/saveddata/booster.py @@ -24,6 +24,8 @@ import datetime from eos.db import saveddata_meta from eos.saveddata.booster import Booster +from eos.saveddata.boosterSideEffect import BoosterSideEffect +from eos.saveddata.fit import Fit boosters_table = Table("boosters", saveddata_meta, Column("ID", Integer, primary_key=True), @@ -34,19 +36,21 @@ boosters_table = Table("boosters", saveddata_meta, Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), ) -# Legacy booster side effect code, should disable but a mapper relies on it. -activeSideEffects_table = Table("boostersActiveSideEffects", saveddata_meta, - Column("boosterID", ForeignKey("boosters.ID"), primary_key=True), - Column("effectID", Integer, primary_key=True)) + +booster_side_effect_table = Table("boosterSideEffects", saveddata_meta, + Column("boosterID", Integer, ForeignKey("boosters.ID"), primary_key=True, index=True), + Column("effectID", Integer, nullable=False, primary_key=True), + Column("active", Boolean, default=False)) -class ActiveSideEffectsDummy(object): - def __init__(self, effectID): - self.effectID = effectID - - -mapper(ActiveSideEffectsDummy, activeSideEffects_table) mapper(Booster, boosters_table, - properties={"_Booster__activeSideEffectDummies": relation(ActiveSideEffectsDummy)}) + properties={ + "_Booster__sideEffects": relation( + BoosterSideEffect, + backref="booster", + cascade='all, delete, delete-orphan'), + } +) -Booster._Booster__activeSideEffectIDs = association_proxy("_Booster__activeSideEffectDummies", "effectID") + +mapper(BoosterSideEffect, booster_side_effect_table) diff --git a/eos/effects/boosterarmorhppenalty.py b/eos/effects/boosterarmorhppenalty.py index a3f09cce3..485a1ad55 100644 --- a/eos/effects/boosterarmorhppenalty.py +++ b/eos/effects/boosterarmorhppenalty.py @@ -3,8 +3,12 @@ # Used by: # Implants from group: Booster (12 of 62) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Armor Capacity" + +# Attribute that this effect targets +attr = "boosterArmorHPPenalty" def handler(fit, booster, context): - fit.ship.boostItemAttr("armorHP", booster.getModifiedItemAttr("boosterArmorHPPenalty")) + fit.ship.boostItemAttr("armorHP", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boosterarmorrepairamountpenalty.py b/eos/effects/boosterarmorrepairamountpenalty.py index ac06a9c22..5f556908b 100644 --- a/eos/effects/boosterarmorrepairamountpenalty.py +++ b/eos/effects/boosterarmorrepairamountpenalty.py @@ -5,9 +5,13 @@ # Implants named like: Mindflood Booster (3 of 4) # Implants named like: Sooth Sayer Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Armor Repair Amount" + +# Attribute that this effect targets +attr = "boosterArmorRepairAmountPenalty" def handler(fit, booster, context): fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Armor Repair Unit", - "armorDamageAmount", booster.getModifiedItemAttr("boosterArmorRepairAmountPenalty")) + "armorDamageAmount", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boostercapacitorcapacitypenalty.py b/eos/effects/boostercapacitorcapacitypenalty.py index 3a8351ac4..087b591f5 100644 --- a/eos/effects/boostercapacitorcapacitypenalty.py +++ b/eos/effects/boostercapacitorcapacitypenalty.py @@ -4,8 +4,12 @@ # Implants named like: Blue Pill Booster (3 of 5) # Implants named like: Exile Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Cap Capacity" + +# Attribute that this effect targets +attr = "boosterCapacitorCapacityPenalty" def handler(fit, booster, context): - fit.ship.boostItemAttr("capacitorCapacity", booster.getModifiedItemAttr("boosterCapacitorCapacityPenalty")) + fit.ship.boostItemAttr("capacitorCapacity", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boostermaxvelocitypenalty.py b/eos/effects/boostermaxvelocitypenalty.py index e76434169..30ea2da82 100644 --- a/eos/effects/boostermaxvelocitypenalty.py +++ b/eos/effects/boostermaxvelocitypenalty.py @@ -3,8 +3,12 @@ # Used by: # Implants from group: Booster (12 of 62) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Velocity" + +# Attribute that this effect targets +attr = "boosterMaxVelocityPenalty" def handler(fit, booster, context): - fit.ship.boostItemAttr("maxVelocity", booster.getModifiedItemAttr("boosterMaxVelocityPenalty")) + fit.ship.boostItemAttr("maxVelocity", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boostermissileexplosioncloudpenaltyfixed.py b/eos/effects/boostermissileexplosioncloudpenaltyfixed.py index 778e4b28c..270f53fc3 100644 --- a/eos/effects/boostermissileexplosioncloudpenaltyfixed.py +++ b/eos/effects/boostermissileexplosioncloudpenaltyfixed.py @@ -4,9 +4,13 @@ # Implants named like: Exile Booster (3 of 4) # Implants named like: Mindflood Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Missile Explosion Radius" + +# Attribute that this effect targets +attr = "boosterMissileAOECloudPenalty" def handler(fit, booster, context): fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), - "aoeCloudSize", booster.getModifiedItemAttr("boosterMissileAOECloudPenalty")) + "aoeCloudSize", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boostermissileexplosionvelocitypenalty.py b/eos/effects/boostermissileexplosionvelocitypenalty.py index e81a9fc96..e9857e13c 100644 --- a/eos/effects/boostermissileexplosionvelocitypenalty.py +++ b/eos/effects/boostermissileexplosionvelocitypenalty.py @@ -3,9 +3,13 @@ # Used by: # Implants named like: Blue Pill Booster (3 of 5) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Missile Explosion Velocity" + +# Attribute that this effect targets +attr = "boosterAOEVelocityPenalty" def handler(fit, booster, context): fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), - "aoeVelocity", booster.getModifiedItemAttr("boosterAOEVelocityPenalty")) + "aoeVelocity", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boostermissilevelocitypenalty.py b/eos/effects/boostermissilevelocitypenalty.py index a4f2d1205..5ffe02dd8 100644 --- a/eos/effects/boostermissilevelocitypenalty.py +++ b/eos/effects/boostermissilevelocitypenalty.py @@ -4,9 +4,13 @@ # Implants named like: Crash Booster (3 of 4) # Implants named like: X Instinct Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Missile Velocity" + +# Attribute that this effect targets +attr = "boosterMissileVelocityPenalty" def handler(fit, booster, context): fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), - "maxVelocity", "boosterMissileVelocityPenalty") + "maxVelocity", attr) diff --git a/eos/effects/boostershieldcapacitypenalty.py b/eos/effects/boostershieldcapacitypenalty.py index 836647f76..0255940b8 100644 --- a/eos/effects/boostershieldcapacitypenalty.py +++ b/eos/effects/boostershieldcapacitypenalty.py @@ -3,8 +3,12 @@ # Used by: # Implants from group: Booster (12 of 62) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Shield Capacity" + +# Attribute that this effect targets +attr = "boosterShieldCapacityPenalty" def handler(fit, booster, context): - fit.ship.boostItemAttr("shieldCapacity", booster.getModifiedItemAttr("boosterShieldCapacityPenalty")) + fit.ship.boostItemAttr("shieldCapacity", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boosterturretfalloffpenalty.py b/eos/effects/boosterturretfalloffpenalty.py index 84f2e2145..f79674987 100644 --- a/eos/effects/boosterturretfalloffpenalty.py +++ b/eos/effects/boosterturretfalloffpenalty.py @@ -4,9 +4,13 @@ # Implants named like: Drop Booster (3 of 4) # Implants named like: X Instinct Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Turret Falloff" + +# Attribute that this effect targets +attr = "boosterTurretFalloffPenalty" def handler(fit, booster, context): fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"), - "falloff", booster.getModifiedItemAttr("boosterTurretFalloffPenalty")) + "falloff", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boosterturretoptimalrangepenalty.py b/eos/effects/boosterturretoptimalrangepenalty.py index d70275c29..e4f9d7e4c 100644 --- a/eos/effects/boosterturretoptimalrangepenalty.py +++ b/eos/effects/boosterturretoptimalrangepenalty.py @@ -5,9 +5,13 @@ # Implants named like: Mindflood Booster (3 of 4) # Implants named like: Sooth Sayer Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Turret Optimal Range" + +# Attribute that this effect targets +attr = "boosterTurretOptimalRange" def handler(fit, booster, context): fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"), - "maxRange", booster.getModifiedItemAttr("boosterTurretOptimalRange")) + "maxRange", booster.getModifiedItemAttr(attr)) diff --git a/eos/effects/boosterturrettrackingpenalty.py b/eos/effects/boosterturrettrackingpenalty.py index 6f268fdc4..0e6025b66 100644 --- a/eos/effects/boosterturrettrackingpenalty.py +++ b/eos/effects/boosterturrettrackingpenalty.py @@ -4,9 +4,13 @@ # Implants named like: Exile Booster (3 of 4) # Implants named like: Frentix Booster (3 of 4) type = "boosterSideEffect" -activeByDefault = False +# User-friendly name for the side effect +displayName = "Turret Tracking" + +# Attribute that this effect targets +attr = "boosterTurretTrackingPenalty" def handler(fit, booster, context): fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"), - "trackingSpeed", booster.getModifiedItemAttr("boosterTurretTrackingPenalty")) + "trackingSpeed", booster.getModifiedItemAttr(attr)) diff --git a/eos/saveddata/booster.py b/eos/saveddata/booster.py index 62eb4d88f..413661e86 100644 --- a/eos/saveddata/booster.py +++ b/eos/saveddata/booster.py @@ -24,6 +24,7 @@ from sqlalchemy.orm import reconstructor, validates import eos.db from eos.effectHandlerHelpers import HandledItem from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut +from eos.saveddata.boosterSideEffect import BoosterSideEffect pyfalog = Logger(__name__) @@ -37,6 +38,9 @@ class Booster(HandledItem, ItemAttrShortcut): self.itemID = item.ID if item is not None else None self.active = True + + self.__sideEffects = self.__getSideEffects() + self.build() @reconstructor @@ -58,34 +62,27 @@ class Booster(HandledItem, ItemAttrShortcut): def build(self): """ Build object. Assumes proper and valid item already set """ - self.__sideEffects = [] self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.overrides = self.__item.overrides self.__slot = self.__calculateSlot(self.__item) - # Legacy booster side effect code, disabling as not currently implemented - ''' - for effect in self.__item.effects.itervalues(): - if effect.isType("boosterSideEffect"): - s = SideEffect(self) - s.effect = effect - s.active = effect.ID in self.__activeSideEffectIDs - self.__sideEffects.append(s) - ''' + if len(self.sideEffects) != len(self.__getSideEffects()): + self.__sideEffects = [] + for ability in self.__getSideEffects(): + self.__sideEffects.append(ability) - # Legacy booster side effect code, disabling as not currently implemented - ''' - def iterSideEffects(self): - return self.__sideEffects.__iter__() + @property + def sideEffects(self): + return self.__sideEffects or [] - def getSideEffect(self, name): - for sideEffect in self.iterSideEffects(): - if sideEffect.effect.name == name: - return sideEffect + @property + def activeSideEffectEffects(self): + return [x.effect for x in self.sideEffects if x.active] - raise KeyError("SideEffect with %s as name not found" % name) - ''' + def __getSideEffects(self): + """Returns list of BoosterSideEffect that are loaded with data""" + return [BoosterSideEffect(effect) for effect in self.item.effects.values() if effect.isType("boosterSideEffect")] @property def itemModifiedAttributes(self): @@ -118,19 +115,14 @@ class Booster(HandledItem, ItemAttrShortcut): return if not self.active: return + for effect in self.item.effects.itervalues(): if effect.runTime == runTime and \ - (effect.isType("passive") or effect.isType("boosterSideEffect")) and \ - effect.activeByDefault: + (effect.isType("passive") or effect.isType("boosterSideEffect")): + if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects: + continue effect.handler(fit, self, ("booster",)) - # Legacy booster code, not fully implemented - ''' - for sideEffect in self.iterSideEffects(): - if sideEffect.active and sideEffect.effect.runTime == runTime: - sideEffect.effect.handler(fit, self, ("boosterSideEffect",)) - ''' - @validates("ID", "itemID", "ammoID", "active") def validator(self, key, val): map = { @@ -161,41 +153,3 @@ class Booster(HandledItem, ItemAttrShortcut): ''' return copy - - -# Legacy booster side effect code, disabling as not currently implemented -''' - class SideEffect(object): - def __init__(self, owner): - self.__owner = owner - self.__active = False - self.__effect = None - - @property - def active(self): - return self.__active - - @active.setter - def active(self, active): - if not isinstance(active, bool): - raise TypeError("Expecting a bool, not a " + type(active)) - - if active != self.__active: - if active: - self.__owner._Booster__activeSideEffectIDs.append(self.effect.ID) - else: - self.__owner._Booster__activeSideEffectIDs.remove(self.effect.ID) - - self.__active = active - - @property - def effect(self): - return self.__effect - - @effect.setter - def effect(self, effect): - if not hasattr(effect, "handler"): - raise TypeError("Need an effect with a handler") - - self.__effect = effect -''' diff --git a/eos/saveddata/boosterSideEffect.py b/eos/saveddata/boosterSideEffect.py new file mode 100644 index 000000000..6f08c0904 --- /dev/null +++ b/eos/saveddata/boosterSideEffect.py @@ -0,0 +1,66 @@ +# =============================================================================== +# Copyright (C) 2010 Diego Duclos +# +# This file is part of eos. +# +# eos is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# eos is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with eos. If not, see . +# =============================================================================== + +from logbook import Logger + +from sqlalchemy.orm import reconstructor + +pyfalog = Logger(__name__) + + +class BoosterSideEffect(object): + + def __init__(self, effect): + """Initialize from the program""" + self.__effect = effect + self.effectID = effect.ID if effect is not None else None + self.active = False + self.build() + + @reconstructor + def init(self): + """Initialize from the database""" + self.__effect = None + + if self.effectID: + self.__effect = next((x for x in self.booster.item.effects.itervalues() if x.ID == self.effectID), None) + if self.__effect is None: + pyfalog.error("Effect (id: {0}) does not exist", self.effectID) + return + + self.build() + + def build(self): + pass + + @property + def effect(self): + return self.__effect + + @property + def name(self): + return "{0}% {1}".format( + self.booster.getModifiedItemAttr(self.attr), + self.__effect.getattr('displayName') or self.__effect.handlerName, + ) + + @property + def attr(self): + return self.__effect.getattr('attr') + diff --git a/gui/boosterView.py b/gui/boosterView.py index 5f43aca04..549c5fd4c 100644 --- a/gui/boosterView.py +++ b/gui/boosterView.py @@ -49,6 +49,7 @@ class BoosterView(d.Display): "State", "attr:boosterness", "Base Name", + "Side Effects", "Price", ] diff --git a/gui/builtinContextMenus/boosterSideEffects.py b/gui/builtinContextMenus/boosterSideEffects.py new file mode 100644 index 000000000..68cfed1b2 --- /dev/null +++ b/gui/builtinContextMenus/boosterSideEffects.py @@ -0,0 +1,64 @@ +# noinspection PyPackageRequirements +import wx +from gui.contextMenu import ContextMenu +import gui.mainFrame +import gui.globalEvents as GE +from service.fit import Fit +from service.settings import ContextMenuSettings + + +class BoosterSideEffect(ContextMenu): + def __init__(self): + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.settings = ContextMenuSettings.getInstance() + + def display(self, srcContext, selection): + # if not self.settings.get('fighterAbilities'): + # return False + + if self.mainFrame.getActiveFit() is None or srcContext not in ("boosterItem"): + return False + + self.booster = selection[0] + return True + + def getText(self, itmContext, selection): + return "Side Effects" + + def addEffect(self, menu, ability): + label = ability.name + id = ContextMenu.nextID() + self.effectIds[id] = ability + menuItem = wx.MenuItem(menu, id, label, kind=wx.ITEM_CHECK) + menu.Bind(wx.EVT_MENU, self.handleMode, menuItem) + return menuItem + + def getSubMenu(self, context, selection, rootMenu, i, pitem): + msw = True if "wxMSW" in wx.PlatformInfo else False + self.context = context + self.effectIds = {} + + sub = wx.Menu() + + for effect in self.booster.sideEffects: + if not effect.effect.isImplemented: + continue + menuItem = self.addEffect(rootMenu if msw else sub, effect) + sub.AppendItem(menuItem) + menuItem.Check(effect.active) + + return sub + + def handleMode(self, event): + effect = self.effectIds[event.Id] + if effect is False or effect not in self.booster.sideEffects: + event.Skip() + return + + sFit = Fit.getInstance() + fitID = self.mainFrame.getActiveFit() + sFit.toggleBoosterSideEffect(fitID, effect) + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) + + +BoosterSideEffect.register() diff --git a/gui/builtinViewColumns/__init__.py b/gui/builtinViewColumns/__init__.py index cff8bc614..2a089e176 100644 --- a/gui/builtinViewColumns/__init__.py +++ b/gui/builtinViewColumns/__init__.py @@ -1,2 +1,2 @@ __all__ = ["ammo", "ammoIcon", "attributeDisplay", "baseIcon", "baseName", - "capacitorUse", "maxRange", "price", "propertyDisplay", "state", "misc", "abilities"] + "capacitorUse", "maxRange", "price", "propertyDisplay", "state", "misc", "abilities", "sideEffects"] diff --git a/gui/builtinViewColumns/sideEffects.py b/gui/builtinViewColumns/sideEffects.py new file mode 100644 index 000000000..25adb3769 --- /dev/null +++ b/gui/builtinViewColumns/sideEffects.py @@ -0,0 +1,46 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + + +# noinspection PyPackageRequirements +import wx +from eos.saveddata.booster import Booster +from gui.viewColumn import ViewColumn +import gui.mainFrame + + +class SideEffects(ViewColumn): + name = "Side Effects" + + def __init__(self, fittingView, params): + ViewColumn.__init__(self, fittingView) + + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.columnText = "Active Side Effects" + self.mask = wx.LIST_MASK_TEXT + + def getText(self, stuff): + if isinstance(stuff, Booster): + active = [x.name for x in stuff.sideEffects if x.active] + if len(active) == 0: + return "None" + return ", ".join(active) + + +SideEffects.register() diff --git a/gui/contextMenu.py b/gui/contextMenu.py index b56661217..043140db3 100644 --- a/gui/contextMenu.py +++ b/gui/contextMenu.py @@ -206,6 +206,7 @@ from gui.builtinContextMenus import ( # noqa: E402,F401 metaSwap, implantSets, fighterAbilities, + boosterSideEffects, commandFits, tabbedFits ) diff --git a/gui/viewColumn.py b/gui/viewColumn.py index 34c3c89ce..08f9ca2bb 100644 --- a/gui/viewColumn.py +++ b/gui/viewColumn.py @@ -79,5 +79,6 @@ from gui.builtinViewColumns import ( # noqa: E402, F401 misc, price, propertyDisplay, - state + state, + sideEffects ) diff --git a/service/fit.py b/service/fit.py index 706fc41cc..543c79f3c 100644 --- a/service/fit.py +++ b/service/fit.py @@ -991,6 +991,13 @@ class Fit(object): eos.db.commit() self.recalc(fit) + def toggleBoosterSideEffect(self, fitID, sideEffect): + pyfalog.debug("Toggling booster side effect for fit ID: {0}", fitID) + fit = eos.db.getFit(fitID) + sideEffect.active = not sideEffect.active + eos.db.commit() + self.recalc(fit) + def changeChar(self, fitID, charID): pyfalog.debug("Changing character ({0}) for fit ID: {1}", charID, fitID) if fitID is None or charID is None: