Merge branch 'feature/booster-side-effects' into development

This commit is contained in:
blitzmann
2017-07-09 18:26:38 -04:00
21 changed files with 291 additions and 103 deletions

View File

@@ -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)

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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)

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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))

View File

@@ -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
'''

View File

@@ -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 <http://www.gnu.org/licenses/>.
# ===============================================================================
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')

View File

@@ -49,6 +49,7 @@ class BoosterView(d.Display):
"State",
"attr:boosterness",
"Base Name",
"Side Effects",
"Price",
]

View File

@@ -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()

View File

@@ -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"]

View File

@@ -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 <http://www.gnu.org/licenses/>.
# =============================================================================
# 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()

View File

@@ -206,6 +206,7 @@ from gui.builtinContextMenus import ( # noqa: E402,F401
metaSwap,
implantSets,
fighterAbilities,
boosterSideEffects,
commandFits,
tabbedFits
)

View File

@@ -79,5 +79,6 @@ from gui.builtinViewColumns import ( # noqa: E402, F401
misc,
price,
propertyDisplay,
state
state,
sideEffects
)

View File

@@ -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: