diff --git a/eos/db/__init__.py b/eos/db/__init__.py
index dd027841b..21b790421 100644
--- a/eos/db/__init__.py
+++ b/eos/db/__init__.py
@@ -77,7 +77,7 @@ sd_lock = threading.RLock()
from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
# noinspection PyPep8
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \
- miscData, module, override, price, queries, skill, targetResists, user
+ miscData, mutator, module, override, price, queries, skill, targetResists, user
# Import queries
# noinspection PyPep8
diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index ba1ddad73..c36517623 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -1,6 +1,7 @@
__all__ = [
"character",
"fit",
+ "mutator",
"module",
"user",
"skill",
diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py
index 149f4f73c..f84de348e 100644
--- a/eos/db/saveddata/module.py
+++ b/eos/db/saveddata/module.py
@@ -23,6 +23,7 @@ import datetime
from eos.db import saveddata_meta
from eos.saveddata.module import Module
+from eos.saveddata.mutator import Mutator
from eos.saveddata.fit import Fit
modules_table = Table("modules", saveddata_meta,
@@ -39,4 +40,11 @@ modules_table = Table("modules", saveddata_meta,
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
mapper(Module, modules_table,
- properties={"owner": relation(Fit)})
+ properties={
+ "owner": relation(Fit),
+ "mutators": relation(
+ Mutator,
+ backref="module",
+ cascade="all,delete-orphan"
+ )
+ })
diff --git a/eos/db/saveddata/mutator.py b/eos/db/saveddata/mutator.py
new file mode 100644
index 000000000..e503a5b25
--- /dev/null
+++ b/eos/db/saveddata/mutator.py
@@ -0,0 +1,35 @@
+# ===============================================================================
+# 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 sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime, Float
+from sqlalchemy.orm import mapper
+import datetime
+
+from eos.db import saveddata_meta
+from eos.saveddata.mutator import Mutator
+
+mutator_table = Table("mutators", saveddata_meta,
+ Column("moduleID", Integer, ForeignKey("modules.ID"), nullable=False, index=True),
+ Column("attrID", Integer, primary_key=True, index=True),
+ Column("value", Float, nullable=False),
+ Column("created", DateTime, nullable=True, default=datetime.datetime.now),
+ Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)
+ )
+
+mapper(Mutator, mutator_table)
diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py
index b4c73c958..70968b1a9 100644
--- a/eos/modifiedAttributeDict.py
+++ b/eos/modifiedAttributeDict.py
@@ -33,6 +33,14 @@ class ItemAttrShortcut(object):
return return_value or default
+ def getBaseAttrValue(self, key, default=0):
+ '''
+ Gets base value in this order:
+ Mutated value > override value > attribute value
+ '''
+ return_value = self.itemModifiedAttributes.getOriginal(key)
+
+ return return_value or default
class ChargeAttrShortcut(object):
def getModifiedChargeAttr(self, key, default=0):
@@ -59,8 +67,10 @@ class ModifiedAttributeDict(collections.MutableMapping):
self.__modified = {}
# Affected by entities
self.__affectedBy = {}
- # Overrides
+ # Overrides (per item)
self.__overrides = {}
+ # Mutators (per module)
+ self.__mutators = {}
# Dictionaries for various value modification types
self.__forced = {}
self.__preAssigns = {}
@@ -100,6 +110,14 @@ class ModifiedAttributeDict(collections.MutableMapping):
def overrides(self, val):
self.__overrides = val
+ @property
+ def mutators(self):
+ return {x.attr.name: x for x in self.__mutators}
+
+ @mutators.setter
+ def mutators(self, val):
+ self.__mutators = val
+
def __getitem__(self, key):
# Check if we have final calculated value
key_value = self.__modified.get(key)
@@ -128,14 +146,16 @@ class ModifiedAttributeDict(collections.MutableMapping):
del self.__intermediary[key]
def getOriginal(self, key, default=None):
+ val = None
if self.overrides_enabled and self.overrides:
- val = self.overrides.get(key, None)
- else:
- val = None
+ val = self.overrides.get(key, val)
+
+ # mutators are overriden by overrides. x_x
+ val = self.mutators.get(key, val)
if val is None:
if self.original:
- val = self.original.get(key, None)
+ val = self.original.get(key, val)
if val is None and val != default:
val = default
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index cf669f273..560caddf5 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -109,7 +109,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__charge = eos.db.getItem(self.chargeID)
self.build()
-
def build(self):
""" Builds internal module variables from both init's """
@@ -129,6 +128,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if self.__item:
self.__itemModifiedAttributes.original = self.__item.attributes
+ self.__itemModifiedAttributes.mutators = self.mutators
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__slot = self.__calculateSlot(self.__item)
diff --git a/eos/saveddata/mutator.py b/eos/saveddata/mutator.py
new file mode 100644
index 000000000..eddc97570
--- /dev/null
+++ b/eos/saveddata/mutator.py
@@ -0,0 +1,54 @@
+# ===============================================================================
+# Copyright (C) 2015 Ryan Holmes
+#
+# 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
+
+import eos.db
+from eos.eqBase import EqBase
+
+pyfalog = Logger(__name__)
+
+
+class Mutator(EqBase):
+ def __init__(self, stuff, attr, value):
+ self.moduleID = stuff.ID
+ self.attrID = attr.ID
+ self.__attr = attr
+ self.value = value
+
+ @reconstructor
+ def init(self):
+ self.__attr = None
+ self.__item = None
+
+ if self.attrID:
+ self.__attr = eos.db.getAttributeInfo(self.attrID)
+ if self.__attr is None:
+ pyfalog.error("Attribute (id: {0}) does not exist", self.attrID)
+ return
+
+ @property
+ def attr(self):
+ return self.__attr
+
+ @property
+ def item(self):
+ return self.__item
diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py
index e40710c45..5bd44e210 100644
--- a/gui/builtinItemStatsViews/itemAttributes.py
+++ b/gui/builtinItemStatsViews/itemAttributes.py
@@ -174,7 +174,12 @@ class ItemParams(wx.Panel):
info = self.attrInfo.get(name)
att = self.attrValues[name]
- valDefault = getattr(info, "value", None)
+ # If we're working with a stuff object, we should get the original value from our getBaseAttrValue function,
+ # which will return the value with respect to the effective base (with mutators / overrides in place)
+ valDefault = getattr(info, "value", None) # Get default value from attribute
+ if self.stuff is not None:
+ # if it's a stuff, overwrite default (with fallback to current value)
+ valDefault = self.stuff.getBaseAttrValue(name, valDefault)
valueDefault = valDefault if valDefault is not None else att
val = getattr(att, "value", None)