Merge branch 'issue/1645'

This commit is contained in:
blitzmann
2018-06-21 00:21:08 -04:00
1895 changed files with 1598 additions and 242 deletions

View File

@@ -78,10 +78,10 @@ sd_lock = threading.RLock()
# Import all the definitions for all our database stuff
# noinspection PyPep8
from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit
from eos.db.gamedata import alphaClones, attribute, category, effect, group, item, marketGroup, metaData, metaGroup, queries, traits, unit, dynamicAttributes
# 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

View File

@@ -1,2 +1,2 @@
__all__ = ["attribute", "category", "effect", "group", "metaData",
"icon", "item", "marketGroup", "metaGroup", "unit", "alphaClones"]
__all__ = ["attribute", "category", "effect", "group", "metaData", "dynamicAttributes",
"item", "marketGroup", "metaGroup", "unit", "alphaClones"]

View File

@@ -22,7 +22,7 @@ from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relation, mapper, synonym, deferred
from eos.db import gamedata_meta
from eos.gamedata import Attribute, AttributeInfo, Unit, Icon
from eos.gamedata import Attribute, AttributeInfo, Unit
typeattributes_table = Table("dgmtypeattribs", gamedata_meta,
Column("value", Float),
@@ -38,7 +38,7 @@ attributes_table = Table("dgmattribs", gamedata_meta,
Column("published", Boolean),
Column("displayName", String),
Column("highIsGood", Boolean),
Column("iconID", Integer, ForeignKey("icons.iconID")),
Column("iconID", Integer),
Column("unitID", Integer, ForeignKey("dgmunits.unitID")))
mapper(Attribute, typeattributes_table,
@@ -46,7 +46,6 @@ mapper(Attribute, typeattributes_table,
mapper(AttributeInfo, attributes_table,
properties={
"icon" : relation(Icon),
"unit" : relation(Unit),
"ID" : synonym("attributeID"),
"name" : synonym("attributeName"),

View File

@@ -21,18 +21,17 @@ from sqlalchemy import Column, String, Integer, ForeignKey, Boolean, Table
from sqlalchemy.orm import relation, mapper, synonym, deferred
from eos.db import gamedata_meta
from eos.gamedata import Category, Icon
from eos.gamedata import Category
categories_table = Table("invcategories", gamedata_meta,
Column("categoryID", Integer, primary_key=True),
Column("categoryName", String),
Column("description", String),
Column("published", Boolean),
Column("iconID", Integer, ForeignKey("icons.iconID")))
Column("iconID", Integer))
mapper(Category, categories_table,
properties={
"icon" : relation(Icon),
"ID" : synonym("categoryID"),
"name" : synonym("categoryName"),
"description": deferred(categories_table.c.description)

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 sqlalchemy import Column, Float, Integer, Table, ForeignKey
from sqlalchemy.orm import mapper, relation, synonym
from sqlalchemy.ext.associationproxy import association_proxy
from eos.db import gamedata_meta
from eos.gamedata import DynamicItem, DynamicItemAttribute, DynamicItemItem, Item
from eos.gamedata import AttributeInfo
dynamic_table = Table("mutaplasmids", gamedata_meta,
Column("typeID", ForeignKey("invtypes.typeID"), primary_key=True, index=True),
Column("resultingTypeID", ForeignKey("invtypes.typeID"), primary_key=True))
dynamicAttributes_table = Table("mutaplasmidAttributes", gamedata_meta,
Column("typeID", Integer, ForeignKey("mutaplasmids.typeID"), primary_key=True),
Column("attributeID", ForeignKey("dgmattribs.attributeID"), primary_key=True),
Column("min", Float),
Column("max", Float))
dynamicApplicable_table = Table("mutaplasmidItems", gamedata_meta,
Column("typeID", ForeignKey("mutaplasmids.typeID"), primary_key=True),
Column("applicableTypeID", ForeignKey("invtypes.typeID"), primary_key=True),
)
mapper(DynamicItem, dynamic_table, properties={
"attributes": relation(DynamicItemAttribute),
"item": relation(Item, foreign_keys=[dynamic_table.c.typeID]),
"resultingItem": relation(Item, foreign_keys=[dynamic_table.c.resultingTypeID]),
"ID": synonym("typeID"),
})
mapper(DynamicItemAttribute, dynamicAttributes_table,
properties={"info": relation(AttributeInfo, lazy=False)})
mapper(DynamicItemItem, dynamicApplicable_table, properties={
"mutaplasmid": relation(DynamicItem),
})
DynamicItemAttribute.ID = association_proxy("info", "attributeID")
DynamicItemAttribute.name = association_proxy("info", "attributeName")
DynamicItemAttribute.description = association_proxy("info", "description")
DynamicItemAttribute.published = association_proxy("info", "published")
DynamicItemAttribute.displayName = association_proxy("info", "displayName")
DynamicItemAttribute.highIsGood = association_proxy("info", "highIsGood")
DynamicItemAttribute.iconID = association_proxy("info", "iconID")
DynamicItemAttribute.icon = association_proxy("info", "icon")
DynamicItemAttribute.unit = association_proxy("info", "unit")

View File

@@ -18,10 +18,10 @@
# ===============================================================================
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
from sqlalchemy.orm import relation, mapper, synonym, deferred
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
from eos.db import gamedata_meta
from eos.gamedata import Category, Group, Icon
from eos.gamedata import Category, Group
groups_table = Table("invgroups", gamedata_meta,
Column("groupID", Integer, primary_key=True),
@@ -29,12 +29,11 @@ groups_table = Table("invgroups", gamedata_meta,
Column("description", String),
Column("published", Boolean),
Column("categoryID", Integer, ForeignKey("invcategories.categoryID")),
Column("iconID", Integer, ForeignKey("icons.iconID")))
Column("iconID", Integer))
mapper(Group, groups_table,
properties={
"category" : relation(Category, backref="groups"),
"icon" : relation(Icon),
"category" : relation(Category, backref=backref("groups", cascade="all,delete")),
"ID" : synonym("groupID"),
"name" : synonym("groupName"),
"description": deferred(groups_table.c.description)

View File

@@ -19,12 +19,13 @@
from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table, Float
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import relation, mapper, synonym, deferred
from sqlalchemy.orm import relation, mapper, synonym, deferred, backref
from sqlalchemy.orm.collections import attribute_mapped_collection
from eos.db.gamedata.effect import typeeffects_table
from eos.db import gamedata_meta
from eos.gamedata import Attribute, Effect, Group, Icon, Item, MetaType, Traits
from eos.gamedata import Attribute, Effect, Group, Item, MetaType, Traits, DynamicItemItem, DynamicItem
from eos.db.gamedata.dynamicAttributes import dynamicApplicable_table, dynamic_table
items_table = Table("invtypes", gamedata_meta,
Column("typeID", Integer, primary_key=True),
@@ -37,7 +38,8 @@ items_table = Table("invtypes", gamedata_meta,
Column("capacity", Float),
Column("published", Boolean),
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
Column("iconID", Integer, ForeignKey("icons.iconID")),
Column("iconID", Integer),
Column("graphicID", Integer),
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
from .metaGroup import metatypes_table # noqa
@@ -45,8 +47,7 @@ from .traits import traits_table # noqa
mapper(Item, items_table,
properties={
"group" : relation(Group, backref="items"),
"icon" : relation(Icon),
"group" : relation(Group, backref=backref("items", cascade="all,delete")),
"_Item__attributes": relation(Attribute, cascade='all, delete, delete-orphan', collection_class=attribute_mapped_collection('name')),
"effects": relation(Effect, secondary=typeeffects_table, collection_class=attribute_mapped_collection('name')),
"metaGroup" : relation(MetaType,
@@ -57,7 +58,13 @@ mapper(Item, items_table,
"description" : deferred(items_table.c.description),
"traits" : relation(Traits,
primaryjoin=traits_table.c.typeID == items_table.c.typeID,
uselist=False)
uselist=False),
"mutaplasmids": relation(DynamicItem,
primaryjoin=dynamicApplicable_table.c.applicableTypeID == items_table.c.typeID,
secondaryjoin=dynamicApplicable_table.c.typeID == DynamicItem.typeID,
secondary=dynamicApplicable_table,
backref="applicableItems"
)
})
Item.category = association_proxy("group", "category")

View File

@@ -21,7 +21,7 @@ from sqlalchemy import Column, String, Integer, Boolean, ForeignKey, Table
from sqlalchemy.orm import relation, mapper, synonym, deferred
from eos.db import gamedata_meta
from eos.gamedata import Icon, Item, MarketGroup
from eos.gamedata import Item, MarketGroup
marketgroups_table = Table("invmarketgroups", gamedata_meta,
Column("marketGroupID", Integer, primary_key=True),
@@ -30,14 +30,13 @@ marketgroups_table = Table("invmarketgroups", gamedata_meta,
Column("hasTypes", Boolean),
Column("parentGroupID", Integer,
ForeignKey("invmarketgroups.marketGroupID", initially="DEFERRED", deferrable=True)),
Column("iconID", Integer, ForeignKey("icons.iconID")))
Column("iconID", Integer))
mapper(MarketGroup, marketgroups_table,
properties={
"items" : relation(Item, backref="marketGroup"),
"parent" : relation(MarketGroup, backref="children",
remote_side=[marketgroups_table.c.marketGroupID]),
"icon" : relation(Icon),
"ID" : synonym("marketGroupID"),
"name" : synonym("marketGroupName"),
"description": deferred(marketgroups_table.c.description)

View File

@@ -17,15 +17,16 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy.orm import join, exc, aliased
from sqlalchemy.orm import join, exc, aliased, joinedload, subqueryload
from sqlalchemy.sql import and_, or_, select
from sqlalchemy.inspection import inspect
import eos.config
from eos.db import gamedata_session
from eos.db.gamedata.metaGroup import metatypes_table, items_table
from eos.db.gamedata.group import groups_table
from eos.db.util import processEager, processWhere
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData
from eos.gamedata import AlphaClone, Attribute, Category, Group, Item, MarketGroup, MetaGroup, AttributeInfo, MetaData, DynamicItem
cache = {}
configVal = getattr(eos.config, "gamedataCache", None)
@@ -97,6 +98,35 @@ def getItem(lookfor, eager=None):
return item
def getMutaplasmid(lookfor, eager=None):
if isinstance(lookfor, int):
item = gamedata_session.query(DynamicItem).filter(DynamicItem.ID == lookfor).first()
else:
raise TypeError("Need integer as argument")
return item
def getItemWithBaseItemAttribute(lookfor, baseItemID, eager=None):
# A lot of this is described in more detail in #1597
item = gamedata_session.query(Item).get(lookfor)
base = getItem(baseItemID)
# we have to load all attributes for this object, otherwise we'll lose access to them when we expunge.
# todo: figure out a way to eagerly load all these via the query...
for x in [*inspect(Item).relationships.keys(), 'description']:
getattr(item, x)
# Copy over the attributes from the base, but ise the items attributes when there's an overlap
# WARNING: the attribute object still has the old typeID. I don't believe we access this typeID anywhere in the code,
# but should keep this in mind for now.
item._Item__attributes = {**base.attributes, **item.attributes}
# Expunge the item form the session. This is required to have different Abyssal / Base combinations loaded in memory.
# Without expunging it, once one Abyssal Web is created, SQLAlchmey will use it for all others. We don't want this,
# we want to generate a completely new object to work with
gamedata_session.expunge(item)
return item
@cachedQuery(1, "lookfor")
def getItems(lookfor, eager=None):
"""

View File

@@ -0,0 +1,18 @@
"""
Migration 28
- adds baseItemID and mutaplasmidID to modules table
"""
import sqlalchemy
def upgrade(saveddata_engine):
try:
saveddata_engine.execute("SELECT baseItemID FROM modules LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN baseItemID INT;")
try:
saveddata_engine.execute("SELECT mutaplasmidID FROM modules LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN mutaplasmidID INT;")

View File

@@ -1,6 +1,7 @@
__all__ = [
"character",
"fit",
"mutator",
"module",
"user",
"skill",

View File

@@ -18,17 +18,21 @@
# ===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm import relation, mapper
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,
Column("ID", Integer, primary_key=True),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
Column("itemID", Integer, nullable=True),
Column("baseItemID", Integer, nullable=True),
Column("mutaplasmidID", Integer, nullable=True),
Column("dummySlot", Integer, nullable=True, default=None),
Column("chargeID", Integer),
Column("state", Integer, CheckConstraint("state >= -1"), CheckConstraint("state <= 2")),
@@ -39,4 +43,12 @@ 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",
collection_class=attribute_mapped_collection('attrID')
)
})

View File

@@ -17,19 +17,19 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from sqlalchemy import Column, String, Integer, Table
from sqlalchemy.orm import mapper, synonym, deferred
from sqlalchemy import Table, Column, Integer, ForeignKey, Boolean, DateTime, Float
from sqlalchemy.orm import mapper
import datetime
from eos.db import gamedata_meta
from eos.gamedata import Icon
from eos.db import saveddata_meta
from eos.saveddata.mutator import Mutator
icons_table = Table("icons", gamedata_meta,
Column("iconID", Integer, primary_key=True),
Column("description", String),
Column("iconFile", String))
mutator_table = Table("mutators", saveddata_meta,
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, 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(Icon, icons_table,
properties={
"ID" : synonym("iconID"),
"description": deferred(icons_table.c.description)
})
mapper(Mutator, mutator_table)

View File

@@ -512,10 +512,17 @@ class Group(EqBase):
pass
class Icon(EqBase):
class DynamicItem(EqBase):
pass
class DynamicItemAttribute(EqBase):
pass
class DynamicItemItem(EqBase):
pass
class MarketGroup(EqBase):
def __repr__(self):
return "MarketGroup(ID={}, name={}, parent={}) at {}".format(

View File

@@ -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.attribute.name: x for x in self.__mutators.values()}
@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

View File

@@ -27,6 +27,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
from eos.enum import Enum
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
from eos.saveddata.citadel import Citadel
from eos.saveddata.mutator import Mutator
pyfalog = Logger(__name__)
@@ -74,15 +75,31 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
MINING_ATTRIBUTES = ("miningAmount",)
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
def __init__(self, item):
def __init__(self, item, baseItem=None, mutaplasmid=None):
"""Initialize a module from the program"""
self.__item = item
self.itemID = item.ID if item is not None else None
self.baseItemID = baseItem.ID if baseItem is not None else None
self.mutaplasmidID = mutaplasmid.ID if mutaplasmid is not None else None
if baseItem is not None:
# we're working with a mutated module, need to get abyssal module loaded with the base attributes
# Note: there may be a better way of doing this, such as a metho on this classe to convert(mutaplamid). This
# will require a bit more research though, considering there has never been a need to "swap" out the item of a Module
# before, and there may be assumptions taken with regards to the item never changing (pre-calculated / cached results, for example)
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
self.__baseItem = baseItem
self.__mutaplasmid = mutaplasmid
else:
self.__item = item
self.__baseItem = baseItem
self.__mutaplasmid = mutaplasmid
if item is not None and self.isInvalid:
raise ValueError("Passed item is not a Module")
self.__charge = None
self.itemID = item.ID if item is not None else None
self.projected = False
self.state = State.ONLINE
self.build()
@@ -91,6 +108,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def init(self):
"""Initialize a module from the database and validate"""
self.__item = None
self.__baseItem = None
self.__charge = None
# we need this early if module is invalid and returns early
@@ -102,6 +120,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return
if self.baseItemID:
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
self.__baseItem = eos.db.getItem(self.baseItemID)
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
if self.__baseItem is None:
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
return
if self.isInvalid:
pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
return
@@ -133,10 +159,23 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__hardpoint = self.__calculateHardpoint(self.__item)
self.__slot = self.__calculateSlot(self.__item)
# Instantiate / remove mutators if this is a mutated module
if self.__baseItem:
for x in self.mutaplasmid.attributes:
attr = self.item.attributes[x.name]
id = attr.ID
if id not in self.mutators: # create the mutator
Mutator(self, attr, attr.value)
# @todo: remove attributes that are no longer part of the mutaplasmid.
self.__itemModifiedAttributes.mutators = self.mutators
if self.__charge:
self.__chargeModifiedAttributes.original = self.__charge.attributes
self.__chargeModifiedAttributes.overrides = self.__charge.overrides
@classmethod
def buildEmpty(cls, slot):
empty = Module(None)
@@ -162,12 +201,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def isInvalid(self):
# todo: validate baseItem as well if it's set.
if self.isEmpty:
return False
return self.__item is None or \
(self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
self.__item.group.name not in self.SYSTEM_GROUPS)
@property
def isMutated(self):
return self.baseItemID or self.mutaplasmidID
@property
def numCharges(self):
if self.charge is None:
@@ -306,6 +350,14 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def item(self):
return self.__item if self.__item != 0 else None
@property
def baseItem(self):
return self.__baseItem if self.__baseItem != 0 else None # what?
@property
def mutaplasmid(self):
return self.__mutaplasmid if self.__mutaplasmid != 0 else None
@property
def charge(self):
return self.__charge if self.__charge != 0 else None
@@ -572,7 +624,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is not None:
g = eos.db.getGroup(int(itemChargeGroup), eager=("items.icon", "items.attributes"))
g = eos.db.getGroup(int(itemChargeGroup), eager=("items.attributes"))
if g is None:
continue
for singleItem in g.items:

130
eos/saveddata/mutator.py Normal file
View File

@@ -0,0 +1,130 @@
# ===============================================================================
# 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 <http://www.gnu.org/licenses/>.
# ===============================================================================
from logbook import Logger
from sqlalchemy.orm import validates, reconstructor
import eos.db
from eos.eqBase import EqBase
pyfalog = Logger(__name__)
class Mutator(EqBase):
""" Mutators are the object that represent an attribute override on the module level, in conjunction with
mutaplasmids. Each mutated module, when created, is instantiated with a list of these objects, dictated by the
mutaplasmid that is used on the base module.
A note on the different attributes on this object:
* attribute: points to the definition of the attribute from dgmattribs.
* baseAttribute: points to the attribute defined for the base item (contains the base value with with to mutate)
* dynamicAttribute: points to the Mutaplasmid definition of the attribute, including min/max
This could probably be cleaned up with smarter relationships, but whatever
"""
def __init__(self, module, attr, value):
# this needs to be above module assignment, as assigning the module will add it to the list and it via
# relationship and needs this set 4correctly
self.attrID = attr.ID
self.module = module
self.moduleID = module.ID
self.__attr = attr
self.build()
self.value = value # must run after the build(), because the validator requires build() to run first
@reconstructor
def init(self):
self.__attr = 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
self.build()
self.value = self.value # run the validator (to ensure we catch any changed min/max values might CCP release)
def build(self):
# try...except here to catch orphaned mutators. Pretty rare, only happens so far if hacking the database
# But put it here to remove the module link if it happens, until a better solution can be developed
try:
# dynamic attribute links to the Mutaplasmids attribute definition for this mutated definition
self.dynamicAttribute = next(a for a in self.module.mutaplasmid.attributes if a.attributeID == self.attrID)
# base attribute links to the base ite's attribute for this mutated definition (contains original, base value)
self.baseAttribute = self.module.item.attributes[self.dynamicAttribute.name]
except:
self.module = None
@validates("value")
def validator(self, key, val):
""" Validates values as properly falling within the range of the modules' Mutaplasmid """
mod = val/self.baseValue
if self.minMod < mod < self.maxMod:
# sweet, all good
returnVal = val
else:
# need to fudge the numbers a bit. Go with the value closest to base
returnVal = min(self.maxValue, max(self.minValue, val))
return returnVal
@property
def isInvalid(self):
# @todo: need to test what happens:
# 1) if an attribute is removed from the EVE database
# 2) if a mutaplasmid does not have the attribute anymore
# 3) if a mutaplasmid does not exist (in eve or on the module's item)
# Can remove invalid ones in a SQLAlchemy collection class... eventually
return self.__attr is None
@property
def highIsGood(self):
return self.attribute.highIsGood
@property
def minMod(self):
return self.dynamicAttribute.min
@property
def maxMod(self):
return self.dynamicAttribute.max
@property
def baseValue(self):
return self.baseAttribute.value
@property
def minValue(self):
return self.minMod * self.baseAttribute.value
@property
def maxValue(self):
return self.maxMod * self.baseAttribute.value
@property
def attribute(self):
return self.__attr

View File

@@ -131,7 +131,7 @@ class Ship(ItemAttrShortcut, HandledItem):
return None
items = []
g = eos.db.getGroup("Ship Modifiers", eager=("items.icon", "items.attributes"))
g = eos.db.getGroup("Ship Modifiers", eager=("items.attributes"))
for item in g.items:
# Rely on name detection because race is not reliable
if item.name.lower().startswith(self.item.name.lower()):