From ea7a5b3c70d1a4313009bb6c8d79e2defd8a3a1e Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Mon, 25 Oct 2021 23:34:08 +0300 Subject: [PATCH] Implement drone mutation support with some exceptions --- eos/db/__init__.py | 2 +- eos/db/gamedata/item.py | 2 +- eos/db/migrations/upgrade45.py | 18 ++++ eos/db/saveddata/__init__.py | 3 +- eos/db/saveddata/drone.py | 20 ++-- eos/db/saveddata/module.py | 13 ++- .../saveddata/{mutator.py => mutatorDrone.py} | 17 ++-- eos/db/saveddata/mutatorMod.py | 36 +++++++ eos/saveddata/drone.py | 45 ++++++--- eos/saveddata/module.py | 99 ++++++------------- eos/saveddata/mutatedMixin.py | 96 ++++++++++++++++++ eos/saveddata/mutator.py | 36 ++++--- gui/builtinContextMenus/__init__.py | 2 +- .../{moduleMutations.py => itemMutations.py} | 34 +++++-- gui/builtinItemStatsViews/itemAttributes.py | 3 +- gui/builtinItemStatsViews/itemMutator.py | 62 +++++++----- gui/fitCommands/__init__.py | 2 + .../gui/localDrone/mutatedConvert.py | 65 ++++++++++++ .../gui/localDrone/mutatedRevert.py | 60 +++++++++++ .../gui/localModule/mutatedConvert.py | 5 +- .../gui/localModule/mutatedRevert.py | 5 +- gui/fitCommands/helpers.py | 31 +++++- gui/itemStats.py | 3 +- service/fit.py | 2 +- 24 files changed, 494 insertions(+), 167 deletions(-) create mode 100644 eos/db/migrations/upgrade45.py rename eos/db/saveddata/{mutator.py => mutatorDrone.py} (63%) create mode 100644 eos/db/saveddata/mutatorMod.py create mode 100644 eos/saveddata/mutatedMixin.py rename gui/builtinContextMenus/{moduleMutations.py => itemMutations.py} (62%) create mode 100644 gui/fitCommands/gui/localDrone/mutatedConvert.py create mode 100644 gui/fitCommands/gui/localDrone/mutatedRevert.py diff --git a/eos/db/__init__.py b/eos/db/__init__.py index a471f83b7..3972c8f8f 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -118,7 +118,7 @@ from eos.db.gamedata import alphaClones, attribute, category, effect, group, ite pyfalog.debug('Importing saveddata DB scheme') # noinspection PyPep8 from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \ - miscData, mutator, module, override, price, queries, skill, targetProfile, user + miscData, mutatorMod, mutatorDrone, module, override, price, queries, skill, targetProfile, user pyfalog.debug('Importing gamedata queries') # noinspection PyPep8 diff --git a/eos/db/gamedata/item.py b/eos/db/gamedata/item.py index e11b072e2..fd6b11789 100644 --- a/eos/db/gamedata/item.py +++ b/eos/db/gamedata/item.py @@ -17,7 +17,7 @@ # along with eos. If not, see . # =============================================================================== -from sqlalchemy import Boolean, Column, Float, ForeignKey, Integer, String, Table +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import backref, deferred, mapper, relation, synonym from sqlalchemy.orm.collections import attribute_mapped_collection diff --git a/eos/db/migrations/upgrade45.py b/eos/db/migrations/upgrade45.py new file mode 100644 index 000000000..ee8a6dc22 --- /dev/null +++ b/eos/db/migrations/upgrade45.py @@ -0,0 +1,18 @@ +""" +Migration 45 + +- Drone mutaplasmid support +""" + +import sqlalchemy + + +def upgrade(saveddata_engine): + try: + saveddata_engine.execute("SELECT baseItemID FROM drones LIMIT 1") + except sqlalchemy.exc.DatabaseError: + saveddata_engine.execute("ALTER TABLE drones ADD COLUMN baseItemID INTEGER;") + try: + saveddata_engine.execute("SELECT mutaplasmidID FROM drones LIMIT 1") + except sqlalchemy.exc.DatabaseError: + saveddata_engine.execute("ALTER TABLE drones ADD COLUMN mutaplasmidID INTEGER;") diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py index 4de25d3cf..804188652 100644 --- a/eos/db/saveddata/__init__.py +++ b/eos/db/saveddata/__init__.py @@ -1,7 +1,8 @@ __all__ = [ "character", "fit", - "mutator", + "mutatorMod", + "mutatorDrone", "module", "user", "skill", diff --git a/eos/db/saveddata/drone.py b/eos/db/saveddata/drone.py index 93434c5b9..dd7337191 100644 --- a/eos/db/saveddata/drone.py +++ b/eos/db/saveddata/drone.py @@ -18,27 +18,35 @@ # =============================================================================== from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime -from sqlalchemy.orm import mapper, relation +from sqlalchemy.orm import mapper, relation, synonym +from sqlalchemy.orm.collections import attribute_mapped_collection import datetime from eos.db import saveddata_meta from eos.saveddata.drone import Drone from eos.saveddata.fit import Fit +from eos.saveddata.mutator import MutatorDrone drones_table = Table("drones", saveddata_meta, Column("groupID", Integer, primary_key=True), Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True), Column("itemID", Integer, nullable=False), + Column("baseItemID", Integer, nullable=True), + Column("mutaplasmidID", Integer, nullable=True), Column("amount", Integer, nullable=False), Column("amountActive", Integer, nullable=False), Column("projected", Boolean, default=False), Column("created", DateTime, nullable=True, default=datetime.datetime.now), Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), - Column("projectionRange", Float, nullable=True) - ) + Column("projectionRange", Float, nullable=True)) + mapper(Drone, drones_table, properties={ - "owner": relation(Fit) - } -) + "ID": synonym("groupID"), + "owner": relation(Fit), + "mutators": relation( + MutatorDrone, + backref="item", + cascade="all,delete-orphan", + collection_class=attribute_mapped_collection('attrID'))}) diff --git a/eos/db/saveddata/module.py b/eos/db/saveddata/module.py index 784eb6de0..83196f7af 100644 --- a/eos/db/saveddata/module.py +++ b/eos/db/saveddata/module.py @@ -18,14 +18,14 @@ # =============================================================================== from sqlalchemy import Table, Column, Integer, Float, ForeignKey, CheckConstraint, Boolean, DateTime -from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm import relation, mapper +from sqlalchemy.orm.collections import attribute_mapped_collection 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 +from eos.saveddata.mutator import MutatorModule modules_table = Table("modules", saveddata_meta, Column("ID", Integer, primary_key=True), @@ -45,13 +45,12 @@ modules_table = Table("modules", saveddata_meta, Column("projectionRange", Float, nullable=True), CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"')) + mapper(Module, modules_table, properties={ "owner": relation(Fit), "mutators": relation( - Mutator, - backref="module", + MutatorModule, + backref="item", cascade="all,delete-orphan", - collection_class=attribute_mapped_collection('attrID') - ) - }) + collection_class=attribute_mapped_collection('attrID'))}) diff --git a/eos/db/saveddata/mutator.py b/eos/db/saveddata/mutatorDrone.py similarity index 63% rename from eos/db/saveddata/mutator.py rename to eos/db/saveddata/mutatorDrone.py index 1ba81e097..9aa20f89c 100644 --- a/eos/db/saveddata/mutator.py +++ b/eos/db/saveddata/mutatorDrone.py @@ -23,13 +23,14 @@ from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table from sqlalchemy.orm import mapper from eos.db import saveddata_meta -from eos.saveddata.mutator import Mutator +from eos.saveddata.mutator import MutatorDrone -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)) +mutatorDrones_table = Table( + "mutatorsDrones", saveddata_meta, + Column("groupID", Integer, ForeignKey("drones.groupID"), 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(Mutator, mutator_table) +mapper(MutatorDrone, mutatorDrones_table) diff --git a/eos/db/saveddata/mutatorMod.py b/eos/db/saveddata/mutatorMod.py new file mode 100644 index 000000000..a17e8a72d --- /dev/null +++ b/eos/db/saveddata/mutatorMod.py @@ -0,0 +1,36 @@ +# =============================================================================== +# 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 . +# =============================================================================== + +import datetime + +from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table +from sqlalchemy.orm import mapper + +from eos.db import saveddata_meta +from eos.saveddata.mutator import MutatorModule + +mutatorMods_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(MutatorModule, mutatorMods_table) diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py index a5cbe1945..0acf5864f 100644 --- a/eos/saveddata/drone.py +++ b/eos/saveddata/drone.py @@ -25,6 +25,8 @@ from sqlalchemy.orm import reconstructor, validates import eos.db from eos.effectHandlerHelpers import HandledCharge, HandledItem from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict +from eos.saveddata.mutatedMixin import MutatedMixin, MutaError +from eos.saveddata.mutator import MutatorDrone from eos.utils.cycles import CycleInfo from eos.utils.default import DEFAULT from eos.utils.stats import DmgTypes, RRTypes @@ -33,12 +35,13 @@ from eos.utils.stats import DmgTypes, RRTypes pyfalog = Logger(__name__) -class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): +class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, MutatedMixin): MINING_ATTRIBUTES = ("miningAmount",) - def __init__(self, item): + def __init__(self, item, baseItem=None, mutaplasmid=None): """Initialize a drone from the program""" - self.__item = item + self._item = item + self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid) if self.isInvalid: raise ValueError("Passed item is not a Drone") @@ -53,14 +56,19 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @reconstructor def init(self): """Initialize a drone from the database and validate""" - self.__item = None + self._item = None if self.itemID: - self.__item = eos.db.getItem(self.itemID) - if self.__item is None: + self._item = eos.db.getItem(self.itemID) + if self._item is None: pyfalog.error("Item (id: {0}) does not exist", self.itemID) return + try: + self._mutaReconstruct() + except MutaError: + return + if self.isInvalid: pyfalog.error("Item (id: {0}) is not a Drone", self.itemID) return @@ -74,10 +82,13 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__baseRRAmount = None self.__miningyield = None self.__itemModifiedAttributes = ModifiedAttributeDict() - self.__itemModifiedAttributes.original = self.__item.attributes - self.__itemModifiedAttributes.overrides = self.__item.overrides - + self.__itemModifiedAttributes.original = self._item.attributes + self.__itemModifiedAttributes.overrides = self._item.overrides self.__chargeModifiedAttributes = ModifiedAttributeDict() + + self._mutaLoadMutators(mutatorClass=MutatorDrone) + self.__itemModifiedAttributes.mutators = self.mutators + # pheonix todo: check the attribute itself, not the modified. this will always return 0 now. chargeID = self.getModifiedItemAttr("entityMissileTypeID", None) if chargeID is not None: @@ -96,11 +107,17 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @property def isInvalid(self): - return self.__item is None or self.__item.category.name != "Drone" + if self._item is None: + return True + if self._item.category.name != "Drone": + return True + if self._mutaIsInvalid: + return True + return False @property def item(self): - return self.__item + return self._item @property def charge(self): @@ -337,10 +354,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect) def __deepcopy__(self, memo): - copy = Drone(self.item) + copy = Drone(self.item, self.baseItem, self.mutaplasmid) copy.amount = self.amount copy.amountActive = self.amountActive copy.projectionRange = self.projectionRange + self._mutaApplyMutators(mutatorClass=MutatorDrone, targetInstance=copy) return copy def rebase(self, item): @@ -348,10 +366,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): amountActive = self.amountActive projectionRange = self.projectionRange - Drone.__init__(self, item) + Drone.__init__(self, item, self.baseItem, self.mutaplasmid) self.amount = amount self.amountActive = amountActive self.projectionRange = projectionRange + self._mutaApplyMutators(mutatorClass=MutatorDrone) def fits(self, fit): fitDroneGroupLimits = set() diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index 680ccd50d..e2602eef3 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -27,7 +27,8 @@ from eos.const import FittingHardpoint, FittingModuleState, FittingSlot from eos.effectHandlerHelpers import HandledCharge, HandledItem from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict from eos.saveddata.citadel import Citadel -from eos.saveddata.mutator import Mutator +from eos.saveddata.mutatedMixin import MutatedMixin, MutaError +from eos.saveddata.mutator import MutatorModule from eos.utils.cycles import CycleInfo, CycleSequence from eos.utils.default import DEFAULT from eos.utils.float import floatUnerr @@ -61,7 +62,7 @@ ProjectedSystem = { } -class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): +class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, MutatedMixin): """An instance of this class represents a module together with its charge and modified attributes""" MINING_ATTRIBUTES = ("miningAmount",) SYSTEM_GROUPS = ( @@ -72,21 +73,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): """Initialize a module from the program""" 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 + self._item = item + self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid) if item is not None and self.isInvalid: raise ValueError("Passed item is not a Module") @@ -101,27 +90,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @reconstructor def init(self): """Initialize a module from the database and validate""" - self.__item = None - self.__baseItem = None + self._item = None self.__charge = None - self.__mutaplasmid = None # we need this early if module is invalid and returns early self.__slot = self.dummySlot if self.itemID: - self.__item = eos.db.getItem(self.itemID) - if self.__item is None: + self._item = eos.db.getItem(self.itemID) + if self._item is None: 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 + try: + self._mutaReconstruct() + except MutaError: + return if self.isInvalid: pyfalog.error("Item (id: {0}) is not a Module", self.itemID) @@ -149,21 +133,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self) self.__slot = self.dummySlot # defaults to None - if self.__item: - self.__itemModifiedAttributes.original = self.__item.attributes - 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. + if self._item: + self.__itemModifiedAttributes.original = self._item.attributes + self.__itemModifiedAttributes.overrides = self._item.overrides + self.__hardpoint = self.__calculateHardpoint(self._item) + self.__slot = self.calculateSlot(self._item) + self._mutaLoadMutators(mutatorClass=MutatorModule) self.__itemModifiedAttributes.mutators = self.mutators if self.__charge: @@ -198,28 +174,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): # todo: validate baseItem as well if it's set. if self.isEmpty: return False - if self.__item is None: + if self._item is None: return True if ( - self.__item.category.name not in ("Module", "Subsystem", "Structure Module") - and self.__item.group.name not in self.SYSTEM_GROUPS + self._item.category.name not in ("Module", "Subsystem", "Structure Module") + and self._item.group.name not in self.SYSTEM_GROUPS ): return True if ( - self.__item.category.name == "Structure Module" - and self.__item.group.name == "Quantum Cores" + self._item.category.name == "Structure Module" + and self._item.group.name == "Quantum Cores" ): return True - if self.item.isAbyssal and not self.isMutated: - return True - if self.isMutated and not self.__mutaplasmid: + if self._mutaIsInvalid: return True return False - @property - def isMutated(self): - return self.baseItemID and self.mutaplasmidID - @property def numCharges(self): return self.getNumCharges(self.charge) @@ -419,15 +389,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @property def item(self): - return self.__item if self.__item != 0 else None - - @property - def baseItem(self): - return self.__baseItem - - @property - def mutaplasmid(self): - return self.__mutaplasmid + return self._item if self._item != 0 else None @property def charge(self): @@ -1098,9 +1060,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): copy.spoolType = self.spoolType copy.spoolAmount = self.spoolAmount copy.projectionRange = self.projectionRange - - for x in self.mutators.values(): - Mutator(copy, x.attribute, x.value) + self._mutaApplyMutators(mutatorClass=MutatorModule, targetInstance=copy) return copy @@ -1118,14 +1078,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.spoolType = spoolType self.spoolAmount = spoolAmount self.projectionRange = projectionRange - for x in self.mutators.values(): - Mutator(self, x.attribute, x.value) + self._mutaApplyMutators(mutatorClass=MutatorModule) def __repr__(self): if self.item: - return "Module(ID={}, name={}) at {}".format( - self.item.ID, self.item.name, hex(id(self)) - ) + return "Module(ID={}, name={}) at {}".format(self.item.ID, self.item.name, hex(id(self))) else: return "EmptyModule() at {}".format(hex(id(self))) diff --git a/eos/saveddata/mutatedMixin.py b/eos/saveddata/mutatedMixin.py new file mode 100644 index 000000000..a1cf270e0 --- /dev/null +++ b/eos/saveddata/mutatedMixin.py @@ -0,0 +1,96 @@ +# =============================================================================== +# 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 . +# =============================================================================== + + +import eos.db + +from logbook import Logger + + +pyfalog = Logger(__name__) + + +class MutaError(Exception): + pass + + +class MutatedMixin: + + @property + def isMutated(self): + return self.baseItemID and self.mutaplasmidID + + @property + def baseItem(self): + return self.__baseItem + + @property + def mutaplasmid(self): + return self.__mutaplasmid + + def _mutaInit(self, baseItem, mutaplasmid): + 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) + print('muta init', self, self._item.ID, self.baseItemID) + self._item = eos.db.getItemWithBaseItemAttribute(self._item.ID, self.baseItemID) + self.__baseItem = baseItem + self.__mutaplasmid = mutaplasmid + else: + self.__baseItem = None + self.__mutaplasmid = None + + def _mutaReconstruct(self): + self.__baseItem = None + self.__mutaplasmid = None + 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) + raise MutaError + + def _mutaLoadMutators(self, mutatorClass): + # 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 + mutatorClass(self, attr, attr.value) + # @todo: remove attributes that are no longer part of the mutaplasmid. + + @property + def _mutaIsInvalid(self): + if self.item.isAbyssal and not self.isMutated: + return True + if self.isMutated and not self.__mutaplasmid: + return True + return False + + def _mutaApplyMutators(self, mutatorClass, targetInstance=None): + if targetInstance is None: + targetInstance = self + for x in self.mutators.values(): + mutatorClass(targetInstance, x.attribute, x.value) diff --git a/eos/saveddata/mutator.py b/eos/saveddata/mutator.py index ee1b116c2..a14af2d2c 100644 --- a/eos/saveddata/mutator.py +++ b/eos/saveddata/mutator.py @@ -27,10 +27,10 @@ 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. +class MutatorBase(EqBase): + """ Mutators are the object that represent an attribute override on the eos item level, in conjunction with + mutaplasmids. Each mutated item, when created, is instantiated with a list of these objects, dictated by the + mutaplasmid that is used on the base item. A note on the different attributes on this object: * attribute: points to the definition of the attribute from dgmattribs. @@ -40,13 +40,13 @@ class Mutator(EqBase): 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 + def __init__(self, item, attr, value): + # this needs to be above item assignment, as assigning the item 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.item = item + self.itemID = item.ID self.__attr = attr self.build() @@ -67,20 +67,20 @@ class Mutator(EqBase): 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 + # But put it here to remove the eos item 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) + self.dynamicAttribute = next(a for a in self.item.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] + self.baseAttribute = self.item.item.attributes[self.dynamicAttribute.name] except (KeyboardInterrupt, SystemExit): raise except: - self.module = None + self.item = None @validates("value") def validator(self, key, val): - """ Validates values as properly falling within the range of the modules' Mutaplasmid """ + """ Validates values as properly falling within the range of the items' Mutaplasmid """ if self.baseValue == 0: return 0 mod = val / self.baseValue @@ -99,7 +99,7 @@ class Mutator(EqBase): # @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) + # 3) if a mutaplasmid does not exist (in eve or on the pyfa item's item) # Can remove invalid ones in a SQLAlchemy collection class... eventually return self.__attr is None @@ -139,3 +139,11 @@ class Mutator(EqBase): @property def attribute(self): return self.__attr + + +class MutatorModule(MutatorBase): + pass + + +class MutatorDrone(MutatorBase): + pass diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py index 0546e7a0f..9b3b1c0b5 100644 --- a/gui/builtinContextMenus/__init__.py +++ b/gui/builtinContextMenus/__init__.py @@ -26,7 +26,7 @@ from gui.builtinContextMenus import itemAmountChange from gui.builtinContextMenus import itemProjectionRange from gui.builtinContextMenus import droneSplitStack from gui.builtinContextMenus import itemVariationChange -from gui.builtinContextMenus import moduleMutations +from gui.builtinContextMenus import itemMutations from gui.builtinContextMenus import moduleFill from gui.builtinContextMenus import moduleMutatedExport from gui.builtinContextMenus import skillAffectors diff --git a/gui/builtinContextMenus/moduleMutations.py b/gui/builtinContextMenus/itemMutations.py similarity index 62% rename from gui/builtinContextMenus/moduleMutations.py rename to gui/builtinContextMenus/itemMutations.py index 5a569e2b7..b401348d3 100644 --- a/gui/builtinContextMenus/moduleMutations.py +++ b/gui/builtinContextMenus/itemMutations.py @@ -1,16 +1,19 @@ -# noinspection PyPackageRequirements +import re +# noinspection PyPackageRequirements import wx import gui.mainFrame from gui.contextMenu import ContextMenuSingle -from gui.fitCommands import GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand +from gui.fitCommands import ( + GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand, + GuiConvertMutatedLocalDroneCommand, GuiRevertMutatedLocalDroneCommand) from service.fit import Fit _t = wx.GetTranslation -class ChangeModuleMutation(ContextMenuSingle): +class ChangeItemMutation(ContextMenuSingle): def __init__(self): self.mainFrame = gui.mainFrame.MainFrame.getInstance() @@ -18,10 +21,10 @@ class ChangeModuleMutation(ContextMenuSingle): def display(self, callingWindow, srcContext, mainItem): - if srcContext != "fittingModule" or self.mainFrame.getActiveFit() is None: + if srcContext not in ("fittingModule", "droneItem") or self.mainFrame.getActiveFit() is None: return False - if mainItem is None or mainItem.isEmpty: + if mainItem is None or getattr(mainItem, 'isEmpty', False): return False if len(mainItem.item.mutaplasmids) == 0 and not mainItem.isMutated: @@ -44,10 +47,13 @@ class ChangeModuleMutation(ContextMenuSingle): for item in mainItem.item.mutaplasmids: label = item.item.name - keywords = ('Decayed', 'Gravid', 'Unstable', 'Exigent', 'Radical') + keywords = ('Decayed', 'Gravid', 'Unstable', 'Radical') for kw in keywords: if item.item.name.startswith(f'{kw} '): label = kw + m = re.match('(?P\S+) (?P\S+) Drone (?P\S+) Mutaplasmid', label) + if m: + label = '{} {}'.format(m.group('mutagrade'), m.group('mutatype')) id = ContextMenuSingle.nextID() self.eventIDs[id] = (item, mainItem) mItem = wx.MenuItem(menu, id, label) @@ -57,13 +63,17 @@ class ChangeModuleMutation(ContextMenuSingle): return sub def handleMenu(self, event): - mutaplasmid, mod = self.eventIDs[event.Id] + mutaplasmid, item = self.eventIDs[event.Id] fitID = self.mainFrame.getActiveFit() fit = Fit.getInstance().getFit(fitID) - if mod in fit.modules: - position = fit.modules.index(mod) + if item in fit.modules: + position = fit.modules.index(item) self.mainFrame.command.Submit(GuiConvertMutatedLocalModuleCommand( fitID=fitID, position=position, mutaplasmid=mutaplasmid)) + elif item in fit.drones: + position = fit.drones.index(item) + self.mainFrame.command.Submit(GuiConvertMutatedLocalDroneCommand( + fitID=fitID, position=position, mutaplasmid=mutaplasmid)) def activate(self, callingWindow, fullContext, mainItem, i): fitID = self.mainFrame.getActiveFit() @@ -72,9 +82,13 @@ class ChangeModuleMutation(ContextMenuSingle): position = fit.modules.index(mainItem) self.mainFrame.command.Submit(GuiRevertMutatedLocalModuleCommand( fitID=fitID, position=position)) + elif mainItem in fit.drones: + position = fit.drones.index(mainItem) + self.mainFrame.command.Submit(GuiRevertMutatedLocalDroneCommand( + fitID=fitID, position=position)) def getBitmap(self, callingWindow, context, mainItem): return None -ChangeModuleMutation.register() +ChangeItemMutation.register() diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py index d6dec97b4..872d10d51 100644 --- a/gui/builtinItemStatsViews/itemAttributes.py +++ b/gui/builtinItemStatsViews/itemAttributes.py @@ -355,4 +355,5 @@ class ItemParams(wx.Panel): fvalue = roundDec(value, digits) else: fvalue = value - return "%s %s" % (fvalue, unit) + unitSuffix = f' {unit}' if unit is not None else '' + return f'{fvalue}{unitSuffix}' diff --git a/gui/builtinItemStatsViews/itemMutator.py b/gui/builtinItemStatsViews/itemMutator.py index 247d3953c..7125766f4 100644 --- a/gui/builtinItemStatsViews/itemMutator.py +++ b/gui/builtinItemStatsViews/itemMutator.py @@ -18,22 +18,22 @@ _t = wx.GetTranslation class ItemMutatorPanel(wx.Panel): - def __init__(self, parent, mod): + def __init__(self, parent, stuff): wx.Panel.__init__(self, parent) - self.stuff = mod + self.stuff = stuff self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) mainSizer = wx.BoxSizer(wx.VERTICAL) headerSizer = wx.BoxSizer(wx.HORIZONTAL) headerSizer.AddStretchSpacer() - itemIcon = BitmapLoader.getStaticBitmap(mod.item.iconID, self, "icons") + itemIcon = BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons") if itemIcon is not None: headerSizer.Add(itemIcon, 0, 0, 0) - mutaIcon = BitmapLoader.getStaticBitmap(mod.mutaplasmid.item.iconID, self, "icons") + mutaIcon = BitmapLoader.getStaticBitmap(stuff.mutaplasmid.item.iconID, self, "icons") if mutaIcon is not None: headerSizer.Add(mutaIcon, 0, wx.LEFT, 0) - sourceItemShort = "{} {}".format(mod.mutaplasmid.item.name.split(" ")[0], mod.baseItem.name) + sourceItemShort = "{} {}".format(stuff.mutaplasmid.item.name.split(" ")[0], stuff.baseItem.name) sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort) font = parent.GetFont() font.SetWeight(wx.BOLD) @@ -43,7 +43,7 @@ class ItemMutatorPanel(wx.Panel): mainSizer.Add(headerSizer, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND, 0) - self.mutaList = ItemMutatorList(self, mod) + self.mutaList = ItemMutatorList(self, stuff) mainSizer.Add(self.mutaList, 1, wx.EXPAND | wx.ALL, 0) mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND, 0) @@ -68,13 +68,13 @@ class ItemMutatorPanel(wx.Panel): class ItemMutatorList(wx.ScrolledWindow): - def __init__(self, parent, mod): + def __init__(self, parent, stuff): wx.ScrolledWindow.__init__(self, parent) self.SetScrollRate(0, 15) self.carryingFitID = gui.mainFrame.MainFrame.getInstance().getActiveFit() self.initialMutations = {} self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) - self.mod = mod + self.stuff = stuff self.timer = None self.isModified = False @@ -91,9 +91,8 @@ class ItemMutatorList(wx.ScrolledWindow): ('Damage Control', 'duration'): True, ('Siege Module', 'siegeLocalLogisticsDurationBonus'): False } - first = True - for m in sorted(mod.mutators.values(), key=lambda x: x.attribute.displayName): + for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName): if m.baseValue == 0: continue if not first: @@ -102,10 +101,10 @@ class ItemMutatorList(wx.ScrolledWindow): self.initialMutations[m.attrID] = m.value - highIsGood = higOverrides.get((mod.item.group.name, m.attribute.name), m.highIsGood) + highIsGood = higOverrides.get((stuff.item.group.name, m.attribute.name), m.highIsGood) # Format: [raw value, modifier applied to base raw value, display value] - range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue)) - range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue)) + range1 = (m.minValue, self._simplifyValue(m, m.minValue)) + range2 = (m.maxValue, self._simplifyValue(m, m.maxValue)) # minValue/maxValue do not always correspond to min/max, because these are # just base value multiplied by minMod/maxMod, and in case base is negative @@ -148,11 +147,11 @@ class ItemMutatorList(wx.ScrolledWindow): headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0) - worseVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worseRange[0]), rounding='dec') + worseVal = ItemParams.FormatValue(*self._preformatValue(m, worseRange[0]), rounding='dec') worseText = wx.StaticText(self, wx.ID_ANY, worseVal) worseText.SetForegroundColour(badColor) - betterVal = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(betterRange[0]), rounding='dec') + betterVal = ItemParams.FormatValue(*self._preformatValue(m, betterRange[0]), rounding='dec') betterText = wx.StaticText(self, wx.ID_ANY, betterVal) betterText.SetForegroundColour(goodColor) @@ -163,23 +162,38 @@ class ItemMutatorList(wx.ScrolledWindow): sizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5) slider = AttributeSlider(parent=self, - baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue), + baseValue=self._simplifyValue(m, sliderBaseValue), minValue=displayMinRange[1], maxValue=displayMaxRange[1], inverse=displayMaxRange is worseRange) - slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False) + slider.SetValue(self._simplifyValue(m, m.value), False) slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue) self.event_mapping[slider] = m sizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10) self.SetSizer(sizer) + def _simplifyValue(self, mutator, value): + if mutator.attribute.unit is None: + return value + return mutator.attribute.unit.SimplifyValue(value) + + def _complicateValue(self, mutator, value): + if mutator.attribute.unit is None: + return value + return mutator.attribute.unit.ComplicateValue(value) + + def _preformatValue(self, mutator, value): + if mutator.attribute.unit is None: + return value, None + return mutator.attribute.unit.PreformatValue(value) + def changeMutatedValue(self, evt): if evt.AffectsModifiedFlag: self.isModified = True m = self.event_mapping[evt.Object] value = evt.Value - value = m.attribute.unit.ComplicateValue(value) + value = self._complicateValue(m, value) sFit = Fit.getInstance() sFit.changeMutatedValuePrelim(m, value) @@ -198,7 +212,7 @@ class ItemMutatorList(wx.ScrolledWindow): sFit = Fit.getInstance() for slider, m in self.event_mapping.items(): value = sFit.changeMutatedValuePrelim(m, m.baseValue) - value = m.attribute.unit.SimplifyValue(value) + value = self._simplifyValue(m, value) slider.SetValue(value, affect_modified_flag=False) evt.Skip() @@ -208,7 +222,7 @@ class ItemMutatorList(wx.ScrolledWindow): for slider, m in self.event_mapping.items(): value = random.uniform(m.minValue, m.maxValue) value = sFit.changeMutatedValuePrelim(m, value) - value = m.attribute.unit.SimplifyValue(value) + value = self._simplifyValue(m, value) slider.SetValue(value, affect_modified_flag=False) evt.Skip() @@ -218,7 +232,7 @@ class ItemMutatorList(wx.ScrolledWindow): for slider, m in self.event_mapping.items(): if m.attrID in self.initialMutations: value = sFit.changeMutatedValuePrelim(m, self.initialMutations[m.attrID]) - value = m.attribute.unit.SimplifyValue(value) + value = self._simplifyValue(m, value) slider.SetValue(value, affect_modified_flag=False) evt.Skip() @@ -226,14 +240,14 @@ class ItemMutatorList(wx.ScrolledWindow): # Submit mutation changes sFit = Fit.getInstance() fit = sFit.getFit(self.carryingFitID) - if self.mod in fit.modules: + if self.stuff in fit.modules: if self.isModified: currentMutation = {} for slider, m in self.event_mapping.items(): # Sliders may have more up-to-date info than mutator in case we changed # value in slider and without confirming it, decided to close window value = slider.GetValue() - value = m.attribute.unit.ComplicateValue(value) + value = self._complicateValue(m, value) if value != m.value: value = sFit.changeMutatedValuePrelim(m, value) currentMutation[m.attrID] = value @@ -242,7 +256,7 @@ class ItemMutatorList(wx.ScrolledWindow): mainFrame = gui.mainFrame.MainFrame.getInstance() mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand( fitID=self.carryingFitID, - position=fit.modules.index(self.mod), + position=fit.modules.index(self.stuff), mutation=currentMutation, oldMutation=self.initialMutations)) for slider in self.event_mapping: diff --git a/gui/fitCommands/__init__.py b/gui/fitCommands/__init__.py index 6e8382ae8..16dcb480a 100644 --- a/gui/fitCommands/__init__.py +++ b/gui/fitCommands/__init__.py @@ -28,6 +28,8 @@ from .gui.localDrone.changeAmount import GuiChangeLocalDroneAmountCommand from .gui.localDrone.changeMetas import GuiChangeLocalDroneMetasCommand from .gui.localDrone.clone import GuiCloneLocalDroneCommand from .gui.localDrone.imprt import GuiImportLocalDronesCommand +from .gui.localDrone.mutatedConvert import GuiConvertMutatedLocalDroneCommand +from .gui.localDrone.mutatedRevert import GuiRevertMutatedLocalDroneCommand from .gui.localDrone.remove import GuiRemoveLocalDronesCommand from .gui.localDrone.stackSplit import GuiSplitLocalDroneStackCommand from .gui.localDrone.stacksMerge import GuiMergeLocalDroneStacksCommand diff --git a/gui/fitCommands/gui/localDrone/mutatedConvert.py b/gui/fitCommands/gui/localDrone/mutatedConvert.py new file mode 100644 index 000000000..bf23581b5 --- /dev/null +++ b/gui/fitCommands/gui/localDrone/mutatedConvert.py @@ -0,0 +1,65 @@ +import math + +import wx + +import eos.db +import gui.mainFrame +from gui import globalEvents as GE +from gui.fitCommands.calc.drone.localAdd import CalcAddLocalDroneCommand +from gui.fitCommands.calc.drone.localRemove import CalcRemoveLocalDroneCommand +from gui.fitCommands.helpers import DroneInfo, InternalCommandHistory +from service.fit import Fit + + +class GuiConvertMutatedLocalDroneCommand(wx.Command): + + def __init__(self, fitID, position, mutaplasmid): + wx.Command.__init__(self, True, 'Convert Local Drone to Mutated') + self.internalHistory = InternalCommandHistory() + self.fitID = fitID + self.position = position + self.itemID = mutaplasmid.resultingItem.ID + self.mutaplasmidID = mutaplasmid.ID + + def Do(self): + sFit = Fit.getInstance() + fit = sFit.getFit(self.fitID) + try: + drone = fit.drones[self.position] + except IndexError: + return False + if drone.isMutated: + return False + info = DroneInfo( + amount=drone.amount, + amountActive=drone.amountActive, + itemID=self.itemID, + baseItemID=drone.item.ID, + mutaplasmidID=self.mutaplasmidID, + mutations={}) + cmdRemove = CalcRemoveLocalDroneCommand( + fitID=self.fitID, + position=self.position, + amount=math.inf) + cmdAdd = CalcAddLocalDroneCommand( + fitID=self.fitID, + droneInfo=info, + forceNewStack=True, + ignoreRestrictions=True) + success = self.internalHistory.submitBatch(cmdRemove, cmdAdd) + eos.db.flush() + sFit.recalc(self.fitID) + sFit.fill(self.fitID) + eos.db.commit() + wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,))) + return success + + def Undo(self): + success = self.internalHistory.undoAll() + eos.db.flush() + sFit = Fit.getInstance() + sFit.recalc(self.fitID) + sFit.fill(self.fitID) + eos.db.commit() + wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,))) + return success diff --git a/gui/fitCommands/gui/localDrone/mutatedRevert.py b/gui/fitCommands/gui/localDrone/mutatedRevert.py new file mode 100644 index 000000000..8a4836536 --- /dev/null +++ b/gui/fitCommands/gui/localDrone/mutatedRevert.py @@ -0,0 +1,60 @@ +import math + +import wx + +import eos.db +import gui.mainFrame +from gui import globalEvents as GE +from gui.fitCommands.calc.drone.localAdd import CalcAddLocalDroneCommand +from gui.fitCommands.calc.drone.localRemove import CalcRemoveLocalDroneCommand +from gui.fitCommands.helpers import DroneInfo, InternalCommandHistory +from service.fit import Fit + + +class GuiRevertMutatedLocalDroneCommand(wx.Command): + + def __init__(self, fitID, position): + wx.Command.__init__(self, True, 'Revert Local Drone from Mutated') + self.internalHistory = InternalCommandHistory() + self.fitID = fitID + self.position = position + + def Do(self): + sFit = Fit.getInstance() + fit = sFit.getFit(self.fitID) + try: + drone = fit.drones[self.position] + except IndexError: + return False + if not drone.isMutated: + return False + info = DroneInfo( + amount=drone.amount, + amountActive=drone.amountActive, + itemID=drone.baseItemID) + cmdRemove = CalcRemoveLocalDroneCommand( + fitID=self.fitID, + position=self.position, + amount=math.inf) + cmdAdd = CalcAddLocalDroneCommand( + fitID=self.fitID, + droneInfo=info, + forceNewStack=True, + ignoreRestrictions=True) + success = self.internalHistory.submitBatch(cmdRemove, cmdAdd) + eos.db.flush() + sFit.recalc(self.fitID) + sFit.fill(self.fitID) + eos.db.commit() + wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,))) + return success + + def Undo(self): + success = self.internalHistory.undoAll() + eos.db.flush() + sFit = Fit.getInstance() + sFit.recalc(self.fitID) + sFit.fill(self.fitID) + eos.db.commit() + wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,))) + return success diff --git a/gui/fitCommands/gui/localModule/mutatedConvert.py b/gui/fitCommands/gui/localModule/mutatedConvert.py index b8cb99909..e166a5d77 100644 --- a/gui/fitCommands/gui/localModule/mutatedConvert.py +++ b/gui/fitCommands/gui/localModule/mutatedConvert.py @@ -21,7 +21,10 @@ class GuiConvertMutatedLocalModuleCommand(wx.Command): def Do(self): sFit = Fit.getInstance() fit = sFit.getFit(self.fitID) - mod = fit.modules[self.position] + try: + mod = fit.modules[self.position] + except IndexError: + return False if mod.isEmpty: return False if mod.isMutated: diff --git a/gui/fitCommands/gui/localModule/mutatedRevert.py b/gui/fitCommands/gui/localModule/mutatedRevert.py index d97439bd7..6dcb1a54f 100644 --- a/gui/fitCommands/gui/localModule/mutatedRevert.py +++ b/gui/fitCommands/gui/localModule/mutatedRevert.py @@ -19,7 +19,10 @@ class GuiRevertMutatedLocalModuleCommand(wx.Command): def Do(self): sFit = Fit.getInstance() fit = sFit.getFit(self.fitID) - mod = fit.modules[self.position] + try: + mod = fit.modules[self.position] + except IndexError: + return False if mod.isEmpty: return False if not mod.isMutated: diff --git a/gui/fitCommands/helpers.py b/gui/fitCommands/helpers.py index 17291ad5f..0a7936542 100644 --- a/gui/fitCommands/helpers.py +++ b/gui/fitCommands/helpers.py @@ -158,8 +158,11 @@ class ModuleInfo: class DroneInfo: - def __init__(self, itemID, amount, amountActive): + def __init__(self, amount, amountActive, itemID, baseItemID=None, mutaplasmidID=None, mutations=None): self.itemID = itemID + self.baseItemID = baseItemID + self.mutaplasmidID = mutaplasmidID + self.mutations = mutations self.amount = amount self.amountActive = amountActive @@ -170,22 +173,40 @@ class DroneInfo: info = cls( itemID=drone.itemID, amount=drone.amount, - amountActive=drone.amountActive) + amountActive=drone.amountActive, + baseItemID=drone.baseItemID, + mutaplasmidID=drone.mutaplasmidID, + mutations={m.attrID: m.value for m in drone.mutators.values()}) return info def toDrone(self): - item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category')) + mkt = Market.getInstance() + item = mkt.getItem(self.itemID, eager=('attributes', 'group.category')) + if self.baseItemID and self.mutaplasmidID: + baseItem = mkt.getItem(self.baseItemID, eager=('attributes', 'group.category')) + mutaplasmid = eos.db.getDynamicItem(self.mutaplasmidID) + else: + baseItem = None + mutaplasmid = None try: - drone = Drone(item) + drone = Drone(item, baseItem=baseItem, mutaplasmid=mutaplasmid) except ValueError: pyfalog.warning('Invalid item: {}'.format(self.itemID)) return None + + if self.mutations is not None: + for attrID, mutator in drone.mutators.items(): + if attrID in self.mutations: + mutator.value = self.mutations[attrID] + drone.amount = self.amount drone.amountActive = self.amountActive return drone def __repr__(self): - return makeReprStr(self, ['itemID', 'amount', 'amountActive']) + return makeReprStr(self, [ + 'itemID', 'amount', 'amountActive', + 'baseItemID', 'mutaplasmidID', 'mutations']) class FighterInfo: diff --git a/gui/itemStats.py b/gui/itemStats.py index bb98cd3c0..040dba9a7 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -22,6 +22,7 @@ import wx import config import gui.mainFrame +from eos.saveddata.drone import Drone from eos.saveddata.module import Module from gui.auxWindow import AuxiliaryFrame from gui.bitmap_loader import BitmapLoader @@ -165,7 +166,7 @@ class ItemStatsContainer(wx.Panel): self.traits = ItemTraits(self.nbContainer, stuff, item) self.nbContainer.AddPage(self.traits, _t("Traits")) - if isinstance(stuff, Module) and stuff.isMutated: + if isinstance(stuff, (Module, Drone)) and stuff.isMutated: self.mutator = ItemMutatorPanel(self.nbContainer, stuff) self.nbContainer.AddPage(self.mutator, _t("Mutations")) diff --git a/service/fit.py b/service/fit.py index 2a7547a7a..778195b9d 100644 --- a/service/fit.py +++ b/service/fit.py @@ -379,7 +379,7 @@ class Fit: return fits def changeMutatedValuePrelim(self, mutator, value): - pyfalog.debug("Changing mutated value for {} / {}: {} => {}".format(mutator.module, mutator.module.mutaplasmid, mutator.value, value)) + pyfalog.debug("Changing mutated value for {} / {}: {} => {}".format(mutator.item, mutator.item.mutaplasmid, mutator.value, value)) if mutator.value != value: mutator.value = value eos.db.flush()