Files
pyfa/gui/fitCommands/helpers.py

421 lines
14 KiB
Python

import math
import wx
from logbook import Logger
import eos.db
from eos.const import FittingModuleState
from eos.saveddata.booster import Booster
from eos.saveddata.cargo import Cargo
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.implant import Implant
from eos.saveddata.module import Module
from service.market import Market
from utils.repr import makeReprStr
pyfalog = Logger(__name__)
class InternalCommandHistory:
def __init__(self):
self.__buffer = wx.CommandProcessor()
def submit(self, command):
return self.__buffer.Submit(command)
def submitBatch(self, *commands):
for command in commands:
if not self.__buffer.Submit(command):
# Undo what we already submitted
for commandToUndo in reversed(self.__buffer.Commands):
if commandToUndo in commands:
self.__buffer.Undo()
return False
return True
def undoAll(self):
undoneCommands = []
# Undo commands one by one, starting from the last
for commandToUndo in reversed(self.__buffer.Commands):
if commandToUndo.Undo():
undoneCommands.append(commandToUndo)
# If undoing fails, redo already undone commands, starting from the last undone
else:
for commandToRedo in reversed(undoneCommands):
if not commandToRedo.Do():
break
self.__buffer.ClearCommands()
return False
self.__buffer.ClearCommands()
return True
def __len__(self):
return len(self.__buffer.Commands)
class ModuleInfo:
def __init__(self, itemID, baseItemID=None, mutaplasmidID=None, mutations=None, chargeID=None, state=None, spoolType=None, spoolAmount=None):
self.itemID = itemID
self.baseItemID = baseItemID
self.mutaplasmidID = mutaplasmidID
self.mutations = mutations
self.chargeID = chargeID
self.state = state
self.spoolType = spoolType
self.spoolAmount = spoolAmount
@classmethod
def fromModule(cls, mod, unmutate=False):
if mod is None:
return None
if unmutate and mod.isMutated:
info = cls(
itemID=mod.baseItemID,
baseItemID=None,
mutaplasmidID=None,
mutations={},
chargeID=mod.chargeID,
state=mod.state,
spoolType=mod.spoolType,
spoolAmount=mod.spoolAmount)
else:
info = cls(
itemID=mod.itemID,
baseItemID=mod.baseItemID,
mutaplasmidID=mod.mutaplasmidID,
mutations={m.attrID: m.value for m in mod.mutators.values()},
chargeID=mod.chargeID,
state=mod.state,
spoolType=mod.spoolType,
spoolAmount=mod.spoolAmount)
return info
def toModule(self, fallbackState=None):
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:
mod = Module(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 mod.mutators.items():
if attrID in self.mutations:
mutator.value = self.mutations[attrID]
if self.spoolType is not None and self.spoolAmount is not None:
mod.spoolType = self.spoolType
mod.spoolAmount = self.spoolAmount
if self.state is not None:
if mod.isValidState(self.state):
mod.state = self.state
else:
mod.state = mod.getMaxState(proposedState=self.state)
elif fallbackState is not None:
if mod.isValidState(fallbackState):
mod.state = fallbackState
if self.chargeID is not None:
charge = mkt.getItem(self.chargeID, eager=('attributes',))
if charge is None:
pyfalog.warning('Cannot set charge {}'.format(self.chargeID))
return None
mod.charge = charge
return mod
def __eq__(self, other):
if not isinstance(other, ModuleInfo):
return False
return all((
self.itemID == other.itemID,
self.baseItemID == other.baseItemID,
self.mutaplasmidID == other.mutaplasmidID,
self.mutations == other.mutations,
self.chargeID == other.chargeID,
self.state == other.state,
self.spoolType == other.spoolType,
self.spoolAmount == other.spoolAmount))
def __repr__(self):
return makeReprStr(self, [
'itemID', 'baseItemID', 'mutaplasmidID', 'mutations',
'chargeID', 'state', 'spoolType', 'spoolAmount'])
class DroneInfo:
def __init__(self, itemID, amount, amountActive):
self.itemID = itemID
self.amount = amount
self.amountActive = amountActive
@classmethod
def fromDrone(cls, drone):
if drone is None:
return None
info = cls(
itemID=drone.itemID,
amount=drone.amount,
amountActive=drone.amountActive)
return info
def toDrone(self):
item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
try:
drone = Drone(item)
except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None
drone.amount = self.amount
drone.amountActive = self.amountActive
return drone
def __repr__(self):
return makeReprStr(self, ['itemID', 'amount', 'amountActive'])
class FighterInfo:
def __init__(self, itemID, amount=None, state=None, abilities=None):
self.itemID = itemID
self.amount = amount
self.state = state
self.abilities = abilities
@classmethod
def fromFighter(cls, fighter):
if fighter is None:
return None
info = cls(
itemID=fighter.itemID,
amount=fighter.amount,
state=fighter.active,
abilities={fa.effectID: fa.active for fa in fighter.abilities})
return info
def toFighter(self):
item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
try:
fighter = Fighter(item)
except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None
if self.amount is not None:
fighter.amount = self.amount
if self.state is not None:
fighter.active = self.state
if self.abilities is not None:
for ability in fighter.abilities:
ability.active = self.abilities.get(ability.effectID, ability.active)
return fighter
def __repr__(self):
return makeReprStr(self, ['itemID', 'amount', 'state', 'abilities'])
class ImplantInfo:
def __init__(self, itemID, state=None):
self.itemID = itemID
self.state = state
@classmethod
def fromImplant(cls, implant):
if implant is None:
return None
info = cls(
itemID=implant.itemID,
state=implant.active)
return info
def toImplant(self):
item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
try:
implant = Implant(item)
except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None
if self.state is not None:
implant.active = self.state
return implant
def __repr__(self):
return makeReprStr(self, ['itemID', 'state'])
class BoosterInfo:
def __init__(self, itemID, state=None, sideEffects=None):
self.itemID = itemID
self.state = state
self.sideEffects = sideEffects
@classmethod
def fromBooster(cls, booster):
if booster is None:
return None
info = cls(
itemID=booster.itemID,
state=booster.active,
sideEffects={se.effectID: se.active for se in booster.sideEffects})
return info
def toBooster(self):
item = Market.getInstance().getItem(self.itemID, eager=('attributes', 'group.category'))
try:
booster = Booster(item)
except ValueError:
pyfalog.warning('Invalid item: {}'.format(self.itemID))
return None
if self.state is not None:
booster.active = self.state
if self.sideEffects is not None:
for sideEffect in booster.sideEffects:
sideEffect.active = self.sideEffects.get(sideEffect.effectID, sideEffect.active)
return booster
def __repr__(self):
return makeReprStr(self, ['itemID', 'state', 'sideEffects'])
class CargoInfo:
def __init__(self, itemID, amount):
self.itemID = itemID
self.amount = amount
@classmethod
def fromCargo(cls, cargo):
if cargo is None:
return None
info = cls(
itemID=cargo.itemID,
amount=cargo.amount)
return info
def toCargo(self):
item = Market.getInstance().getItem(self.itemID)
cargo = Cargo(item)
cargo.amount = self.amount
return cargo
def __repr__(self):
return makeReprStr(self, ['itemID', 'amount'])
def activeStateLimit(itemIdentity):
item = Market.getInstance().getItem(itemIdentity)
if {
'moduleBonusAssaultDamageControl', 'moduleBonusIndustrialInvulnerability',
'microJumpDrive', 'microJumpPortalDrive', 'emergencyHullEnergizer',
'cynosuralGeneration', 'jumpPortalGeneration', 'jumpPortalGenerationBO',
'cloneJumpAccepting', 'cloakingWarpSafe', 'cloakingPrototype', 'cloaking',
'massEntanglerEffect5', 'electronicAttributeModifyOnline', 'targetPassively'
}.intersection(item.effects):
return FittingModuleState.ONLINE
return FittingModuleState.ACTIVE
def droneStackLimit(fit, itemIdentity):
item = Market.getInstance().getItem(itemIdentity)
hardLimit = max(5, fit.extraAttributes["maxActiveDrones"])
releaseLimit = fit.getReleaseLimitForDrone(item)
limit = min(hardLimit, releaseLimit if releaseLimit > 0 else math.inf)
return limit
def restoreCheckedStates(fit, stateInfo, ignoreModPoss=()):
if stateInfo is None:
return
changedMods, changedProjMods, changedProjDrones = stateInfo
for pos, state in changedMods.items():
if pos in ignoreModPoss:
continue
fit.modules[pos].state = state
for pos, state in changedProjMods.items():
fit.projectedModules[pos].state = state
for pos, amountActive in changedProjDrones.items():
fit.projectedDrones[pos].amountActive = amountActive
def restoreRemovedDummies(fit, dummyInfo):
# Need this to properly undo the case when removal of subsystems removes dummy slots
for position in sorted(dummyInfo):
slot = dummyInfo[position]
fit.modules.insert(position, Module.buildEmpty(slot))
def getSimilarModPositions(mods, mainMod):
sMkt = Market.getInstance()
mainGroupID = getattr(sMkt.getGroupByItem(mainMod.item), 'ID', None)
mainMktGroupID = getattr(sMkt.getMarketGroupByItem(mainMod.item), 'ID', None)
mainEffects = set(getattr(mainMod.item, 'effects', ()))
positions = []
for position, mod in enumerate(mods):
if mod.isEmpty:
continue
# Always include selected module itself
if mod is mainMod:
positions.append(position)
continue
if mod.itemID is None:
continue
# Modules which have the same item ID
if mod.itemID == mainMod.itemID:
positions.append(position)
continue
# And modules from the same group and market group too
modGroupID = getattr(sMkt.getGroupByItem(mod.item), 'ID', None)
modMktGroupID = getattr(sMkt.getMarketGroupByItem(mod.item), 'ID', None)
modEffects = set(getattr(mod.item, 'effects', ()))
if (
modGroupID is not None and modGroupID == mainGroupID and
modMktGroupID is not None and modMktGroupID == mainMktGroupID and
modEffects == mainEffects
):
positions.append(position)
continue
return positions
def getSimilarFighters(fighters, mainFighter):
sMkt = Market.getInstance()
mainGroupID = getattr(sMkt.getGroupByItem(mainFighter.item), 'ID', None)
mainAbilityIDs = set(a.effectID for a in mainFighter.abilities)
similarFighters = []
for fighter in fighters:
# Always include selected fighter itself
if fighter is mainFighter:
similarFighters.append(fighter)
continue
if fighter.itemID is None:
continue
# Fighters which have the same item ID
if fighter.itemID == mainFighter.itemID:
similarFighters.append(fighter)
continue
# And fighters from the same group and with the same abilities too
fighterGroupID = getattr(sMkt.getGroupByItem(fighter.item), 'ID', None)
fighterAbilityIDs = set(a.effectID for a in fighter.abilities)
if (
fighterGroupID is not None and fighterGroupID == mainGroupID and
len(fighterAbilityIDs) > 0 and fighterAbilityIDs == mainAbilityIDs
):
similarFighters.append(fighter)
continue
return similarFighters