Implement drone mutation support with some exceptions

This commit is contained in:
DarkPhoenix
2021-10-25 23:34:08 +03:00
parent 056685ded5
commit ea7a5b3c70
24 changed files with 494 additions and 167 deletions

View File

@@ -118,7 +118,7 @@ from eos.db.gamedata import alphaClones, attribute, category, effect, group, ite
pyfalog.debug('Importing saveddata DB scheme') pyfalog.debug('Importing saveddata DB scheme')
# noinspection PyPep8 # noinspection PyPep8
from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, \ 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') pyfalog.debug('Importing gamedata queries')
# noinspection PyPep8 # noinspection PyPep8

View File

@@ -17,7 +17,7 @@
# along with eos. If not, see <http://www.gnu.org/licenses/>. # along with eos. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================== # ===============================================================================
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.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, deferred, mapper, relation, synonym from sqlalchemy.orm import backref, deferred, mapper, relation, synonym
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection

View File

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

View File

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

View File

@@ -18,27 +18,35 @@
# =============================================================================== # ===============================================================================
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, Boolean, DateTime 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 import datetime
from eos.db import saveddata_meta from eos.db import saveddata_meta
from eos.saveddata.drone import Drone from eos.saveddata.drone import Drone
from eos.saveddata.fit import Fit from eos.saveddata.fit import Fit
from eos.saveddata.mutator import MutatorDrone
drones_table = Table("drones", saveddata_meta, drones_table = Table("drones", saveddata_meta,
Column("groupID", Integer, primary_key=True), Column("groupID", Integer, primary_key=True),
Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True), Column("fitID", Integer, ForeignKey("fits.ID"), nullable=False, index=True),
Column("itemID", Integer, nullable=False), Column("itemID", Integer, nullable=False),
Column("baseItemID", Integer, nullable=True),
Column("mutaplasmidID", Integer, nullable=True),
Column("amount", Integer, nullable=False), Column("amount", Integer, nullable=False),
Column("amountActive", Integer, nullable=False), Column("amountActive", Integer, nullable=False),
Column("projected", Boolean, default=False), Column("projected", Boolean, default=False),
Column("created", DateTime, nullable=True, default=datetime.datetime.now), Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, onupdate=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, mapper(Drone, drones_table,
properties={ 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'))})

View File

@@ -18,14 +18,14 @@
# =============================================================================== # ===============================================================================
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, CheckConstraint, Boolean, DateTime 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 import relation, mapper
from sqlalchemy.orm.collections import attribute_mapped_collection
import datetime import datetime
from eos.db import saveddata_meta from eos.db import saveddata_meta
from eos.saveddata.module import Module from eos.saveddata.module import Module
from eos.saveddata.mutator import Mutator
from eos.saveddata.fit import Fit from eos.saveddata.fit import Fit
from eos.saveddata.mutator import MutatorModule
modules_table = Table("modules", saveddata_meta, modules_table = Table("modules", saveddata_meta,
Column("ID", Integer, primary_key=True), Column("ID", Integer, primary_key=True),
@@ -45,13 +45,12 @@ modules_table = Table("modules", saveddata_meta,
Column("projectionRange", Float, nullable=True), Column("projectionRange", Float, nullable=True),
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"')) CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
mapper(Module, modules_table, mapper(Module, modules_table,
properties={ properties={
"owner": relation(Fit), "owner": relation(Fit),
"mutators": relation( "mutators": relation(
Mutator, MutatorModule,
backref="module", backref="item",
cascade="all,delete-orphan", cascade="all,delete-orphan",
collection_class=attribute_mapped_collection('attrID') collection_class=attribute_mapped_collection('attrID'))})
)
})

View File

@@ -23,13 +23,14 @@ from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, Table
from sqlalchemy.orm import mapper from sqlalchemy.orm import mapper
from eos.db import saveddata_meta from eos.db import saveddata_meta
from eos.saveddata.mutator import Mutator from eos.saveddata.mutator import MutatorDrone
mutator_table = Table("mutators", saveddata_meta, mutatorDrones_table = Table(
Column("moduleID", Integer, ForeignKey("modules.ID"), primary_key=True, index=True), "mutatorsDrones", saveddata_meta,
Column("attrID", Integer, primary_key=True, index=True), Column("groupID", Integer, ForeignKey("drones.groupID"), primary_key=True, index=True),
Column("value", Float, nullable=False), Column("attrID", Integer, primary_key=True, index=True),
Column("created", DateTime, nullable=True, default=datetime.datetime.now), Column("value", Float, nullable=False),
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)) 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)

View File

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

View File

@@ -25,6 +25,8 @@ from sqlalchemy.orm import reconstructor, validates
import eos.db import eos.db
from eos.effectHandlerHelpers import HandledCharge, HandledItem from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict 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.cycles import CycleInfo
from eos.utils.default import DEFAULT from eos.utils.default import DEFAULT
from eos.utils.stats import DmgTypes, RRTypes from eos.utils.stats import DmgTypes, RRTypes
@@ -33,12 +35,13 @@ from eos.utils.stats import DmgTypes, RRTypes
pyfalog = Logger(__name__) pyfalog = Logger(__name__)
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, MutatedMixin):
MINING_ATTRIBUTES = ("miningAmount",) MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item): def __init__(self, item, baseItem=None, mutaplasmid=None):
"""Initialize a drone from the program""" """Initialize a drone from the program"""
self.__item = item self._item = item
self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid)
if self.isInvalid: if self.isInvalid:
raise ValueError("Passed item is not a Drone") raise ValueError("Passed item is not a Drone")
@@ -53,14 +56,19 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@reconstructor @reconstructor
def init(self): def init(self):
"""Initialize a drone from the database and validate""" """Initialize a drone from the database and validate"""
self.__item = None self._item = None
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self._item = eos.db.getItem(self.itemID)
if self.__item is None: if self._item is None:
pyfalog.error("Item (id: {0}) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
try:
self._mutaReconstruct()
except MutaError:
return
if self.isInvalid: if self.isInvalid:
pyfalog.error("Item (id: {0}) is not a Drone", self.itemID) pyfalog.error("Item (id: {0}) is not a Drone", self.itemID)
return return
@@ -74,10 +82,13 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__baseRRAmount = None self.__baseRRAmount = None
self.__miningyield = None self.__miningyield = None
self.__itemModifiedAttributes = ModifiedAttributeDict() self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.original = self._item.attributes
self.__itemModifiedAttributes.overrides = self.__item.overrides self.__itemModifiedAttributes.overrides = self._item.overrides
self.__chargeModifiedAttributes = ModifiedAttributeDict() 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. # pheonix todo: check the attribute itself, not the modified. this will always return 0 now.
chargeID = self.getModifiedItemAttr("entityMissileTypeID", None) chargeID = self.getModifiedItemAttr("entityMissileTypeID", None)
if chargeID is not None: if chargeID is not None:
@@ -96,11 +107,17 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property @property
def isInvalid(self): 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 @property
def item(self): def item(self):
return self.__item return self._item
@property @property
def charge(self): def charge(self):
@@ -337,10 +354,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect) effect.handler(fit, self, ("droneCharge",), projectionRange, effect=effect)
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
copy = Drone(self.item) copy = Drone(self.item, self.baseItem, self.mutaplasmid)
copy.amount = self.amount copy.amount = self.amount
copy.amountActive = self.amountActive copy.amountActive = self.amountActive
copy.projectionRange = self.projectionRange copy.projectionRange = self.projectionRange
self._mutaApplyMutators(mutatorClass=MutatorDrone, targetInstance=copy)
return copy return copy
def rebase(self, item): def rebase(self, item):
@@ -348,10 +366,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
amountActive = self.amountActive amountActive = self.amountActive
projectionRange = self.projectionRange projectionRange = self.projectionRange
Drone.__init__(self, item) Drone.__init__(self, item, self.baseItem, self.mutaplasmid)
self.amount = amount self.amount = amount
self.amountActive = amountActive self.amountActive = amountActive
self.projectionRange = projectionRange self.projectionRange = projectionRange
self._mutaApplyMutators(mutatorClass=MutatorDrone)
def fits(self, fit): def fits(self, fit):
fitDroneGroupLimits = set() fitDroneGroupLimits = set()

View File

@@ -27,7 +27,8 @@ from eos.const import FittingHardpoint, FittingModuleState, FittingSlot
from eos.effectHandlerHelpers import HandledCharge, HandledItem from eos.effectHandlerHelpers import HandledCharge, HandledItem
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
from eos.saveddata.citadel import Citadel 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.cycles import CycleInfo, CycleSequence
from eos.utils.default import DEFAULT from eos.utils.default import DEFAULT
from eos.utils.float import floatUnerr 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""" """An instance of this class represents a module together with its charge and modified attributes"""
MINING_ATTRIBUTES = ("miningAmount",) MINING_ATTRIBUTES = ("miningAmount",)
SYSTEM_GROUPS = ( SYSTEM_GROUPS = (
@@ -72,21 +73,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""Initialize a module from the program""" """Initialize a module from the program"""
self.itemID = item.ID if item is not None else None 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: self._item = item
# we're working with a mutated module, need to get abyssal module loaded with the base attributes self._mutaInit(baseItem=baseItem, mutaplasmid=mutaplasmid)
# 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: if item is not None and self.isInvalid:
raise ValueError("Passed item is not a Module") raise ValueError("Passed item is not a Module")
@@ -101,27 +90,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@reconstructor @reconstructor
def init(self): def init(self):
"""Initialize a module from the database and validate""" """Initialize a module from the database and validate"""
self.__item = None self._item = None
self.__baseItem = None
self.__charge = None self.__charge = None
self.__mutaplasmid = None
# we need this early if module is invalid and returns early # we need this early if module is invalid and returns early
self.__slot = self.dummySlot self.__slot = self.dummySlot
if self.itemID: if self.itemID:
self.__item = eos.db.getItem(self.itemID) self._item = eos.db.getItem(self.itemID)
if self.__item is None: if self._item is None:
pyfalog.error("Item (id: {0}) does not exist", self.itemID) pyfalog.error("Item (id: {0}) does not exist", self.itemID)
return return
if self.baseItemID: try:
self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID) self._mutaReconstruct()
self.__baseItem = eos.db.getItem(self.baseItemID) except MutaError:
self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID) return
if self.__baseItem is None:
pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
return
if self.isInvalid: if self.isInvalid:
pyfalog.error("Item (id: {0}) is not a Module", self.itemID) 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.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
self.__slot = self.dummySlot # defaults to None self.__slot = self.dummySlot # defaults to None
if self.__item: if self._item:
self.__itemModifiedAttributes.original = self.__item.attributes self.__itemModifiedAttributes.original = self._item.attributes
self.__itemModifiedAttributes.overrides = self.__item.overrides self.__itemModifiedAttributes.overrides = self._item.overrides
self.__hardpoint = self.__calculateHardpoint(self.__item) self.__hardpoint = self.__calculateHardpoint(self._item)
self.__slot = self.calculateSlot(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._mutaLoadMutators(mutatorClass=MutatorModule)
self.__itemModifiedAttributes.mutators = self.mutators self.__itemModifiedAttributes.mutators = self.mutators
if self.__charge: if self.__charge:
@@ -198,28 +174,22 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# todo: validate baseItem as well if it's set. # todo: validate baseItem as well if it's set.
if self.isEmpty: if self.isEmpty:
return False return False
if self.__item is None: if self._item is None:
return True return True
if ( if (
self.__item.category.name not in ("Module", "Subsystem", "Structure Module") self._item.category.name not in ("Module", "Subsystem", "Structure Module")
and self.__item.group.name not in self.SYSTEM_GROUPS and self._item.group.name not in self.SYSTEM_GROUPS
): ):
return True return True
if ( if (
self.__item.category.name == "Structure Module" self._item.category.name == "Structure Module"
and self.__item.group.name == "Quantum Cores" and self._item.group.name == "Quantum Cores"
): ):
return True return True
if self.item.isAbyssal and not self.isMutated: if self._mutaIsInvalid:
return True
if self.isMutated and not self.__mutaplasmid:
return True return True
return False return False
@property
def isMutated(self):
return self.baseItemID and self.mutaplasmidID
@property @property
def numCharges(self): def numCharges(self):
return self.getNumCharges(self.charge) return self.getNumCharges(self.charge)
@@ -419,15 +389,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property @property
def item(self): def item(self):
return self.__item if self.__item != 0 else None return self._item if self._item != 0 else None
@property
def baseItem(self):
return self.__baseItem
@property
def mutaplasmid(self):
return self.__mutaplasmid
@property @property
def charge(self): def charge(self):
@@ -1098,9 +1060,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
copy.spoolType = self.spoolType copy.spoolType = self.spoolType
copy.spoolAmount = self.spoolAmount copy.spoolAmount = self.spoolAmount
copy.projectionRange = self.projectionRange copy.projectionRange = self.projectionRange
self._mutaApplyMutators(mutatorClass=MutatorModule, targetInstance=copy)
for x in self.mutators.values():
Mutator(copy, x.attribute, x.value)
return copy return copy
@@ -1118,14 +1078,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.spoolType = spoolType self.spoolType = spoolType
self.spoolAmount = spoolAmount self.spoolAmount = spoolAmount
self.projectionRange = projectionRange self.projectionRange = projectionRange
for x in self.mutators.values(): self._mutaApplyMutators(mutatorClass=MutatorModule)
Mutator(self, x.attribute, x.value)
def __repr__(self): def __repr__(self):
if self.item: if self.item:
return "Module(ID={}, name={}) at {}".format( return "Module(ID={}, name={}) at {}".format(self.item.ID, self.item.name, hex(id(self)))
self.item.ID, self.item.name, hex(id(self))
)
else: else:
return "EmptyModule() at {}".format(hex(id(self))) return "EmptyModule() at {}".format(hex(id(self)))

View File

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

View File

@@ -27,10 +27,10 @@ from eos.eqBase import EqBase
pyfalog = Logger(__name__) pyfalog = Logger(__name__)
class Mutator(EqBase): class MutatorBase(EqBase):
""" Mutators are the object that represent an attribute override on the module level, in conjunction with """ Mutators are the object that represent an attribute override on the eos item level, in conjunction with
mutaplasmids. Each mutated module, when created, is instantiated with a list of these objects, dictated by the mutaplasmids. Each mutated item, when created, is instantiated with a list of these objects, dictated by the
mutaplasmid that is used on the base module. mutaplasmid that is used on the base item.
A note on the different attributes on this object: A note on the different attributes on this object:
* attribute: points to the definition of the attribute from dgmattribs. * 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 This could probably be cleaned up with smarter relationships, but whatever
""" """
def __init__(self, module, attr, value): def __init__(self, item, attr, value):
# this needs to be above module assignment, as assigning the module will add it to the list and it via # 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 # relationship and needs this set 4correctly
self.attrID = attr.ID self.attrID = attr.ID
self.module = module self.item = item
self.moduleID = module.ID self.itemID = item.ID
self.__attr = attr self.__attr = attr
self.build() self.build()
@@ -67,20 +67,20 @@ class Mutator(EqBase):
def build(self): def build(self):
# try...except here to catch orphaned mutators. Pretty rare, only happens so far if hacking the database # 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: try:
# dynamic attribute links to the Mutaplasmids attribute definition for this mutated definition # 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) # 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): except (KeyboardInterrupt, SystemExit):
raise raise
except: except:
self.module = None self.item = None
@validates("value") @validates("value")
def validator(self, key, val): 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: if self.baseValue == 0:
return 0 return 0
mod = val / self.baseValue mod = val / self.baseValue
@@ -99,7 +99,7 @@ class Mutator(EqBase):
# @todo: need to test what happens: # @todo: need to test what happens:
# 1) if an attribute is removed from the EVE database # 1) if an attribute is removed from the EVE database
# 2) if a mutaplasmid does not have the attribute anymore # 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 # Can remove invalid ones in a SQLAlchemy collection class... eventually
return self.__attr is None return self.__attr is None
@@ -139,3 +139,11 @@ class Mutator(EqBase):
@property @property
def attribute(self): def attribute(self):
return self.__attr return self.__attr
class MutatorModule(MutatorBase):
pass
class MutatorDrone(MutatorBase):
pass

View File

@@ -26,7 +26,7 @@ from gui.builtinContextMenus import itemAmountChange
from gui.builtinContextMenus import itemProjectionRange from gui.builtinContextMenus import itemProjectionRange
from gui.builtinContextMenus import droneSplitStack from gui.builtinContextMenus import droneSplitStack
from gui.builtinContextMenus import itemVariationChange from gui.builtinContextMenus import itemVariationChange
from gui.builtinContextMenus import moduleMutations from gui.builtinContextMenus import itemMutations
from gui.builtinContextMenus import moduleFill from gui.builtinContextMenus import moduleFill
from gui.builtinContextMenus import moduleMutatedExport from gui.builtinContextMenus import moduleMutatedExport
from gui.builtinContextMenus import skillAffectors from gui.builtinContextMenus import skillAffectors

View File

@@ -1,16 +1,19 @@
# noinspection PyPackageRequirements import re
# noinspection PyPackageRequirements
import wx import wx
import gui.mainFrame import gui.mainFrame
from gui.contextMenu import ContextMenuSingle from gui.contextMenu import ContextMenuSingle
from gui.fitCommands import GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand from gui.fitCommands import (
GuiConvertMutatedLocalModuleCommand, GuiRevertMutatedLocalModuleCommand,
GuiConvertMutatedLocalDroneCommand, GuiRevertMutatedLocalDroneCommand)
from service.fit import Fit from service.fit import Fit
_t = wx.GetTranslation _t = wx.GetTranslation
class ChangeModuleMutation(ContextMenuSingle): class ChangeItemMutation(ContextMenuSingle):
def __init__(self): def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.mainFrame = gui.mainFrame.MainFrame.getInstance()
@@ -18,10 +21,10 @@ class ChangeModuleMutation(ContextMenuSingle):
def display(self, callingWindow, srcContext, mainItem): 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 return False
if mainItem is None or mainItem.isEmpty: if mainItem is None or getattr(mainItem, 'isEmpty', False):
return False return False
if len(mainItem.item.mutaplasmids) == 0 and not mainItem.isMutated: if len(mainItem.item.mutaplasmids) == 0 and not mainItem.isMutated:
@@ -44,10 +47,13 @@ class ChangeModuleMutation(ContextMenuSingle):
for item in mainItem.item.mutaplasmids: for item in mainItem.item.mutaplasmids:
label = item.item.name label = item.item.name
keywords = ('Decayed', 'Gravid', 'Unstable', 'Exigent', 'Radical') keywords = ('Decayed', 'Gravid', 'Unstable', 'Radical')
for kw in keywords: for kw in keywords:
if item.item.name.startswith(f'{kw} '): if item.item.name.startswith(f'{kw} '):
label = kw label = kw
m = re.match('(?P<mutagrade>\S+) (?P<dronetype>\S+) Drone (?P<mutatype>\S+) Mutaplasmid', label)
if m:
label = '{} {}'.format(m.group('mutagrade'), m.group('mutatype'))
id = ContextMenuSingle.nextID() id = ContextMenuSingle.nextID()
self.eventIDs[id] = (item, mainItem) self.eventIDs[id] = (item, mainItem)
mItem = wx.MenuItem(menu, id, label) mItem = wx.MenuItem(menu, id, label)
@@ -57,13 +63,17 @@ class ChangeModuleMutation(ContextMenuSingle):
return sub return sub
def handleMenu(self, event): def handleMenu(self, event):
mutaplasmid, mod = self.eventIDs[event.Id] mutaplasmid, item = self.eventIDs[event.Id]
fitID = self.mainFrame.getActiveFit() fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID) fit = Fit.getInstance().getFit(fitID)
if mod in fit.modules: if item in fit.modules:
position = fit.modules.index(mod) position = fit.modules.index(item)
self.mainFrame.command.Submit(GuiConvertMutatedLocalModuleCommand( self.mainFrame.command.Submit(GuiConvertMutatedLocalModuleCommand(
fitID=fitID, position=position, mutaplasmid=mutaplasmid)) 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): def activate(self, callingWindow, fullContext, mainItem, i):
fitID = self.mainFrame.getActiveFit() fitID = self.mainFrame.getActiveFit()
@@ -72,9 +82,13 @@ class ChangeModuleMutation(ContextMenuSingle):
position = fit.modules.index(mainItem) position = fit.modules.index(mainItem)
self.mainFrame.command.Submit(GuiRevertMutatedLocalModuleCommand( self.mainFrame.command.Submit(GuiRevertMutatedLocalModuleCommand(
fitID=fitID, position=position)) 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): def getBitmap(self, callingWindow, context, mainItem):
return None return None
ChangeModuleMutation.register() ChangeItemMutation.register()

View File

@@ -355,4 +355,5 @@ class ItemParams(wx.Panel):
fvalue = roundDec(value, digits) fvalue = roundDec(value, digits)
else: else:
fvalue = value fvalue = value
return "%s %s" % (fvalue, unit) unitSuffix = f' {unit}' if unit is not None else ''
return f'{fvalue}{unitSuffix}'

View File

@@ -18,22 +18,22 @@ _t = wx.GetTranslation
class ItemMutatorPanel(wx.Panel): class ItemMutatorPanel(wx.Panel):
def __init__(self, parent, mod): def __init__(self, parent, stuff):
wx.Panel.__init__(self, parent) wx.Panel.__init__(self, parent)
self.stuff = mod self.stuff = stuff
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer = wx.BoxSizer(wx.VERTICAL)
headerSizer = wx.BoxSizer(wx.HORIZONTAL) headerSizer = wx.BoxSizer(wx.HORIZONTAL)
headerSizer.AddStretchSpacer() headerSizer.AddStretchSpacer()
itemIcon = BitmapLoader.getStaticBitmap(mod.item.iconID, self, "icons") itemIcon = BitmapLoader.getStaticBitmap(stuff.item.iconID, self, "icons")
if itemIcon is not None: if itemIcon is not None:
headerSizer.Add(itemIcon, 0, 0, 0) 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: if mutaIcon is not None:
headerSizer.Add(mutaIcon, 0, wx.LEFT, 0) 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) sourceItemText = wx.StaticText(self, wx.ID_ANY, sourceItemShort)
font = parent.GetFont() font = parent.GetFont()
font.SetWeight(wx.BOLD) font.SetWeight(wx.BOLD)
@@ -43,7 +43,7 @@ class ItemMutatorPanel(wx.Panel):
mainSizer.Add(headerSizer, 0, wx.ALL | wx.EXPAND, 5) 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) 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(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) 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): class ItemMutatorList(wx.ScrolledWindow):
def __init__(self, parent, mod): def __init__(self, parent, stuff):
wx.ScrolledWindow.__init__(self, parent) wx.ScrolledWindow.__init__(self, parent)
self.SetScrollRate(0, 15) self.SetScrollRate(0, 15)
self.carryingFitID = gui.mainFrame.MainFrame.getInstance().getActiveFit() self.carryingFitID = gui.mainFrame.MainFrame.getInstance().getActiveFit()
self.initialMutations = {} self.initialMutations = {}
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))
self.mod = mod self.stuff = stuff
self.timer = None self.timer = None
self.isModified = False self.isModified = False
@@ -91,9 +91,8 @@ class ItemMutatorList(wx.ScrolledWindow):
('Damage Control', 'duration'): True, ('Damage Control', 'duration'): True,
('Siege Module', 'siegeLocalLogisticsDurationBonus'): False ('Siege Module', 'siegeLocalLogisticsDurationBonus'): False
} }
first = True 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: if m.baseValue == 0:
continue continue
if not first: if not first:
@@ -102,10 +101,10 @@ class ItemMutatorList(wx.ScrolledWindow):
self.initialMutations[m.attrID] = m.value 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] # Format: [raw value, modifier applied to base raw value, display value]
range1 = (m.minValue, m.attribute.unit.SimplifyValue(m.minValue)) range1 = (m.minValue, self._simplifyValue(m, m.minValue))
range2 = (m.maxValue, m.attribute.unit.SimplifyValue(m.maxValue)) range2 = (m.maxValue, self._simplifyValue(m, m.maxValue))
# minValue/maxValue do not always correspond to min/max, because these are # 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 # 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) 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 = wx.StaticText(self, wx.ID_ANY, worseVal)
worseText.SetForegroundColour(badColor) 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 = wx.StaticText(self, wx.ID_ANY, betterVal)
betterText.SetForegroundColour(goodColor) betterText.SetForegroundColour(goodColor)
@@ -163,23 +162,38 @@ class ItemMutatorList(wx.ScrolledWindow):
sizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5) sizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
slider = AttributeSlider(parent=self, slider = AttributeSlider(parent=self,
baseValue=m.attribute.unit.SimplifyValue(sliderBaseValue), baseValue=self._simplifyValue(m, sliderBaseValue),
minValue=displayMinRange[1], minValue=displayMinRange[1],
maxValue=displayMaxRange[1], maxValue=displayMaxRange[1],
inverse=displayMaxRange is worseRange) 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) slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m self.event_mapping[slider] = m
sizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10) sizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10)
self.SetSizer(sizer) 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): def changeMutatedValue(self, evt):
if evt.AffectsModifiedFlag: if evt.AffectsModifiedFlag:
self.isModified = True self.isModified = True
m = self.event_mapping[evt.Object] m = self.event_mapping[evt.Object]
value = evt.Value value = evt.Value
value = m.attribute.unit.ComplicateValue(value) value = self._complicateValue(m, value)
sFit = Fit.getInstance() sFit = Fit.getInstance()
sFit.changeMutatedValuePrelim(m, value) sFit.changeMutatedValuePrelim(m, value)
@@ -198,7 +212,7 @@ class ItemMutatorList(wx.ScrolledWindow):
sFit = Fit.getInstance() sFit = Fit.getInstance()
for slider, m in self.event_mapping.items(): for slider, m in self.event_mapping.items():
value = sFit.changeMutatedValuePrelim(m, m.baseValue) value = sFit.changeMutatedValuePrelim(m, m.baseValue)
value = m.attribute.unit.SimplifyValue(value) value = self._simplifyValue(m, value)
slider.SetValue(value, affect_modified_flag=False) slider.SetValue(value, affect_modified_flag=False)
evt.Skip() evt.Skip()
@@ -208,7 +222,7 @@ class ItemMutatorList(wx.ScrolledWindow):
for slider, m in self.event_mapping.items(): for slider, m in self.event_mapping.items():
value = random.uniform(m.minValue, m.maxValue) value = random.uniform(m.minValue, m.maxValue)
value = sFit.changeMutatedValuePrelim(m, value) value = sFit.changeMutatedValuePrelim(m, value)
value = m.attribute.unit.SimplifyValue(value) value = self._simplifyValue(m, value)
slider.SetValue(value, affect_modified_flag=False) slider.SetValue(value, affect_modified_flag=False)
evt.Skip() evt.Skip()
@@ -218,7 +232,7 @@ class ItemMutatorList(wx.ScrolledWindow):
for slider, m in self.event_mapping.items(): for slider, m in self.event_mapping.items():
if m.attrID in self.initialMutations: if m.attrID in self.initialMutations:
value = sFit.changeMutatedValuePrelim(m, self.initialMutations[m.attrID]) 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) slider.SetValue(value, affect_modified_flag=False)
evt.Skip() evt.Skip()
@@ -226,14 +240,14 @@ class ItemMutatorList(wx.ScrolledWindow):
# Submit mutation changes # Submit mutation changes
sFit = Fit.getInstance() sFit = Fit.getInstance()
fit = sFit.getFit(self.carryingFitID) fit = sFit.getFit(self.carryingFitID)
if self.mod in fit.modules: if self.stuff in fit.modules:
if self.isModified: if self.isModified:
currentMutation = {} currentMutation = {}
for slider, m in self.event_mapping.items(): for slider, m in self.event_mapping.items():
# Sliders may have more up-to-date info than mutator in case we changed # 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 in slider and without confirming it, decided to close window
value = slider.GetValue() value = slider.GetValue()
value = m.attribute.unit.ComplicateValue(value) value = self._complicateValue(m, value)
if value != m.value: if value != m.value:
value = sFit.changeMutatedValuePrelim(m, value) value = sFit.changeMutatedValuePrelim(m, value)
currentMutation[m.attrID] = value currentMutation[m.attrID] = value
@@ -242,7 +256,7 @@ class ItemMutatorList(wx.ScrolledWindow):
mainFrame = gui.mainFrame.MainFrame.getInstance() mainFrame = gui.mainFrame.MainFrame.getInstance()
mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand( mainFrame.getCommandForFit(self.carryingFitID).Submit(cmd.GuiChangeLocalModuleMutationCommand(
fitID=self.carryingFitID, fitID=self.carryingFitID,
position=fit.modules.index(self.mod), position=fit.modules.index(self.stuff),
mutation=currentMutation, mutation=currentMutation,
oldMutation=self.initialMutations)) oldMutation=self.initialMutations))
for slider in self.event_mapping: for slider in self.event_mapping:

View File

@@ -28,6 +28,8 @@ from .gui.localDrone.changeAmount import GuiChangeLocalDroneAmountCommand
from .gui.localDrone.changeMetas import GuiChangeLocalDroneMetasCommand from .gui.localDrone.changeMetas import GuiChangeLocalDroneMetasCommand
from .gui.localDrone.clone import GuiCloneLocalDroneCommand from .gui.localDrone.clone import GuiCloneLocalDroneCommand
from .gui.localDrone.imprt import GuiImportLocalDronesCommand 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.remove import GuiRemoveLocalDronesCommand
from .gui.localDrone.stackSplit import GuiSplitLocalDroneStackCommand from .gui.localDrone.stackSplit import GuiSplitLocalDroneStackCommand
from .gui.localDrone.stacksMerge import GuiMergeLocalDroneStacksCommand from .gui.localDrone.stacksMerge import GuiMergeLocalDroneStacksCommand

View File

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

View File

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

View File

@@ -21,7 +21,10 @@ class GuiConvertMutatedLocalModuleCommand(wx.Command):
def Do(self): def Do(self):
sFit = Fit.getInstance() sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID) fit = sFit.getFit(self.fitID)
mod = fit.modules[self.position] try:
mod = fit.modules[self.position]
except IndexError:
return False
if mod.isEmpty: if mod.isEmpty:
return False return False
if mod.isMutated: if mod.isMutated:

View File

@@ -19,7 +19,10 @@ class GuiRevertMutatedLocalModuleCommand(wx.Command):
def Do(self): def Do(self):
sFit = Fit.getInstance() sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID) fit = sFit.getFit(self.fitID)
mod = fit.modules[self.position] try:
mod = fit.modules[self.position]
except IndexError:
return False
if mod.isEmpty: if mod.isEmpty:
return False return False
if not mod.isMutated: if not mod.isMutated:

View File

@@ -158,8 +158,11 @@ class ModuleInfo:
class DroneInfo: class DroneInfo:
def __init__(self, itemID, amount, amountActive): def __init__(self, amount, amountActive, itemID, baseItemID=None, mutaplasmidID=None, mutations=None):
self.itemID = itemID self.itemID = itemID
self.baseItemID = baseItemID
self.mutaplasmidID = mutaplasmidID
self.mutations = mutations
self.amount = amount self.amount = amount
self.amountActive = amountActive self.amountActive = amountActive
@@ -170,22 +173,40 @@ class DroneInfo:
info = cls( info = cls(
itemID=drone.itemID, itemID=drone.itemID,
amount=drone.amount, 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 return info
def toDrone(self): 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: try:
drone = Drone(item) drone = Drone(item, baseItem=baseItem, mutaplasmid=mutaplasmid)
except ValueError: except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID)) pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None 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.amount = self.amount
drone.amountActive = self.amountActive drone.amountActive = self.amountActive
return drone return drone
def __repr__(self): def __repr__(self):
return makeReprStr(self, ['itemID', 'amount', 'amountActive']) return makeReprStr(self, [
'itemID', 'amount', 'amountActive',
'baseItemID', 'mutaplasmidID', 'mutations'])
class FighterInfo: class FighterInfo:

View File

@@ -22,6 +22,7 @@ import wx
import config import config
import gui.mainFrame import gui.mainFrame
from eos.saveddata.drone import Drone
from eos.saveddata.module import Module from eos.saveddata.module import Module
from gui.auxWindow import AuxiliaryFrame from gui.auxWindow import AuxiliaryFrame
from gui.bitmap_loader import BitmapLoader from gui.bitmap_loader import BitmapLoader
@@ -165,7 +166,7 @@ class ItemStatsContainer(wx.Panel):
self.traits = ItemTraits(self.nbContainer, stuff, item) self.traits = ItemTraits(self.nbContainer, stuff, item)
self.nbContainer.AddPage(self.traits, _t("Traits")) 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.mutator = ItemMutatorPanel(self.nbContainer, stuff)
self.nbContainer.AddPage(self.mutator, _t("Mutations")) self.nbContainer.AddPage(self.mutator, _t("Mutations"))

View File

@@ -379,7 +379,7 @@ class Fit:
return fits return fits
def changeMutatedValuePrelim(self, mutator, value): 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: if mutator.value != value:
mutator.value = value mutator.value = value
eos.db.flush() eos.db.flush()