Files
pyfa/service/port/eft.py
2025-01-13 23:27:26 +01:00

1068 lines
37 KiB
Python

# =============================================================================
# Copyright (C) 2014 Ryan Holmes
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
import re
from logbook import Logger
from eos.const import FittingModuleState, FittingSlot
from eos.db.gamedata.queries import getDynamicItem
from eos.saveddata.booster import Booster
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.fit import Fit
from eos.saveddata.implant import Implant
from eos.saveddata.module import Module
from eos.saveddata.ship import Ship
from gui.fitCommands.helpers import activeStateLimit
from service.const import PortEftOptions
from service.fit import Fit as svcFit
from service.market import Market
from service.port.muta import parseMutant, renderMutant
from service.port.shared import fetchItem
pyfalog = Logger(__name__)
MODULE_CATS = ('Module', 'Subsystem', 'Structure Module')
SLOT_ORDER = (FittingSlot.LOW, FittingSlot.MED, FittingSlot.HIGH, FittingSlot.RIG, FittingSlot.SUBSYSTEM, FittingSlot.SERVICE)
OFFLINE_SUFFIX = '/OFFLINE'
NAME_CHARS = r'[^,/\[\]]' # Characters which are allowed to be used in name
class MutationExportData:
def __init__(self):
self.reference = 1
self.mutants = {}
def formatMutants(self):
mutationLines = []
if self.mutants:
for mutantReference in sorted(self.mutants):
mutant = self.mutants[mutantReference]
mutationLines.append(renderMutant(mutant, firstPrefix='[{}] '.format(mutantReference), prefix=' '))
return '\n'.join(mutationLines)
def exportEft(fit, options, callback):
mutaData = MutationExportData()
# EFT formatted export is split in several sections, each section is
# separated from another using 2 blank lines. Sections might have several
# sub-sections, which are separated by 1 blank line
sections = []
header = '[{}, {}]'.format(fit.ship.item.typeName, fit.name)
# Section 1: modules, rigs, subsystems, services
moduleExport = exportModules(fit.modules, options, mutaData=mutaData)
if moduleExport:
sections.append(moduleExport)
# Section 2: drones, fighters
minionSection = []
droneExport = exportDrones(
fit.drones, exportMutants=options[PortEftOptions.MUTATIONS],
mutaData=mutaData, standAlone=False)
if droneExport:
minionSection.append(droneExport)
fighterExport = exportFighters(fit.fighters)
if fighterExport:
minionSection.append(fighterExport)
if minionSection:
sections.append('\n\n'.join(minionSection))
# Section 3: implants, boosters
charSection = []
if options[PortEftOptions.IMPLANTS]:
implantExport = exportImplants(fit.implants)
if implantExport:
charSection.append(implantExport)
if options[PortEftOptions.BOOSTERS]:
boosterExport = exportBoosters(fit.boosters)
if boosterExport:
charSection.append(boosterExport)
if charSection:
sections.append('\n\n'.join(charSection))
# Section 4: cargo
if options[PortEftOptions.CARGO]:
cargoExport = exportCargo(fit.cargo)
if cargoExport:
sections.append(cargoExport)
# Section 5: mutated items' details
if options[PortEftOptions.MUTATIONS]:
mutationExport = mutaData.formatMutants()
if mutationExport:
sections.append(mutationExport)
text = '{}\n\n{}'.format(header, '\n\n\n'.join(sections))
if callback:
callback(text)
else:
return text
def exportModules(modules, options, mutaData=None):
if mutaData is None:
mutaData = MutationExportData()
modsBySlotType = {}
for module in modules:
modsBySlotType.setdefault(module.slot, []).append(module)
modSection = []
for slotType in SLOT_ORDER:
rackLines = []
rackModules = modsBySlotType.get(slotType, ())
for module in rackModules:
if module.item:
# if module was mutated, use base item name for export
if module.isMutated:
modName = module.baseItem.typeName
else:
modName = module.item.typeName
if module.isMutated and options[PortEftOptions.MUTATIONS]:
mutaData.mutants[mutaData.reference] = module
mutationSuffix = ' [{}]'.format(mutaData.reference)
mutaData.reference += 1
else:
mutationSuffix = ''
modOfflineSuffix = ' {}'.format(OFFLINE_SUFFIX) if module.state == FittingModuleState.OFFLINE else ''
if module.charge and options[PortEftOptions.LOADED_CHARGES]:
rackLines.append('{}, {}{}{}'.format(
modName, module.charge.typeName, modOfflineSuffix, mutationSuffix))
else:
rackLines.append('{}{}{}'.format(modName, modOfflineSuffix, mutationSuffix))
else:
rackLines.append('[Empty {} slot]'.format(
FittingSlot(slotType).name.capitalize() if slotType is not None else ''))
if rackLines:
modSection.append('\n'.join(rackLines))
return '\n\n'.join(modSection)
def exportDrones(drones, exportMutants=True, mutaData=None, standAlone=True):
# Same as in drone additions panel
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
def getDroneName(drone):
if drone.isMutated:
return drone.baseItem.typeName
return drone.item.typeName
def droneSorter(drone):
if drone.isMutated:
item = drone.baseItem
else:
item = drone.item
groupName = Market.getInstance().getMarketGroupByItem(item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
if mutaData is None:
mutaData = MutationExportData()
sections = []
droneLines = []
for drone in sorted(drones, key=droneSorter):
if drone.isMutated and exportMutants:
mutaData.mutants[mutaData.reference] = drone
mutationSuffix = ' [{}]'.format(mutaData.reference)
mutaData.reference += 1
else:
mutationSuffix = ''
droneLines.append('{} x{}{}'.format(getDroneName(drone), drone.amount, mutationSuffix))
if droneLines:
sections.append('\n'.join(droneLines))
if exportMutants and mutaData.mutants and standAlone:
sections.append(mutaData.formatMutants())
return '\n\n\n'.join(sections)
def exportFighters(fighters):
# Same as in drone additions panel
FIGHTER_ORDER = (
'Light Fighter', 'Structure Light Fighter',
'Heavy Fighter', 'Structure Heavy Fighter',
'Support Fighter', 'Structure Support Fighter')
def fighterSorter(fighter):
groupName = Market.getInstance().getGroupByItem(fighter.item).name
return (FIGHTER_ORDER.index(groupName), fighter.item.typeName)
fighterLines = []
for fighter in sorted(fighters, key=fighterSorter):
fighterLines.append('{} x{}'.format(fighter.item.typeName, fighter.amount))
return '\n'.join(fighterLines)
def exportImplants(implants):
implantLines = []
for implant in sorted(implants, key=lambda i: i.slot or 0):
implantLines.append(implant.item.typeName)
return '\n'.join(implantLines)
def exportBoosters(boosters):
boosterLines = []
for booster in sorted(boosters, key=lambda b: b.slot or 0):
boosterLines.append(booster.item.typeName)
return '\n'.join(boosterLines)
def exportCargo(cargos):
cargoLines = []
for cargo in sorted(cargos, key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.typeName)):
cargoLines.append('{} x{}'.format(cargo.item.typeName, cargo.amount))
return '\n'.join(cargoLines)
def importEft(lines):
lines = _importPrepare(lines)
try:
fit = _importCreateFit(lines)
except EftImportError:
return
aFit = AbstractFit()
aFit.mutations = importGetMutationData(lines)
stubPattern = r'^\[.+?\]$'
modulePattern = r'^(?P<typeName>{0}+?)(,\s*(?P<chargeName>{0}+?))?(?P<offline>\s*{1})?(\s*\[(?P<mutation>\d+?)\])?$'.format(NAME_CHARS, OFFLINE_SUFFIX)
droneCargoPattern = r'^(?P<typeName>{}+?) x(?P<amount>\d+?)(\s*\[(?P<mutation>\d+?)\])?$'.format(NAME_CHARS)
sections = []
for section in _importSectionIter(lines):
for line in section.lines:
# Stub line
if re.match(stubPattern, line):
section.itemSpecs.append(None)
continue
# Items with quantity specifier
m = re.match(droneCargoPattern, line)
if m:
try:
itemSpec = MultiItemSpec(m.group('typeName'))
# Items which cannot be fetched are considered as stubs
except EftImportError:
section.itemSpecs.append(None)
else:
itemSpec.amount = int(m.group('amount'))
section.itemSpecs.append(itemSpec)
if m.group('mutation'):
itemSpec.mutationIdx = int(m.group('mutation'))
continue
# All other items
m = re.match(modulePattern, line)
if m:
try:
itemSpec = RegularItemSpec(m.group('typeName'), chargeName=m.group('chargeName'))
# Items which cannot be fetched are considered as stubs
except EftImportError:
section.itemSpecs.append(None)
else:
if m.group('offline'):
itemSpec.offline = True
if m.group('mutation'):
itemSpec.mutationIdx = int(m.group('mutation'))
section.itemSpecs.append(itemSpec)
continue
_clearTail(section.itemSpecs)
sections.append(section)
hasDroneBay = any(s.isDroneBay for s in sections)
hasFighterBay = any(s.isFighterBay for s in sections)
for section in sections:
if section.isModuleRack:
aFit.addModules(section.itemSpecs)
elif section.isImplantRack:
for itemSpec in section.itemSpecs:
aFit.addImplant(itemSpec)
elif section.isDroneBay:
for itemSpec in section.itemSpecs:
aFit.addDrone(itemSpec)
elif section.isFighterBay:
for itemSpec in section.itemSpecs:
aFit.addFighter(itemSpec)
elif section.isCargoHold:
for itemSpec in section.itemSpecs:
aFit.addCargo(itemSpec)
# Mix between different kinds of item specs (can happen when some
# blank lines are removed)
else:
for itemSpec in section.itemSpecs:
if itemSpec is None:
continue
if itemSpec.isModule:
aFit.addModule(itemSpec)
elif itemSpec.isImplant:
aFit.addImplant(itemSpec)
elif itemSpec.isDrone and not hasDroneBay:
aFit.addDrone(itemSpec)
elif itemSpec.isFighter and not hasFighterBay:
aFit.addFighter(itemSpec)
elif itemSpec.isCargo:
aFit.addCargo(itemSpec)
# Subsystems first because they modify slot amount
for i, m in enumerate(aFit.subsystems):
if m is None:
dummy = Module.buildEmpty(aFit.getSlotByContainer(aFit.subsystems))
dummy.owner = fit
fit.modules.replaceRackPosition(i, dummy)
elif m.fits(fit):
m.owner = fit
fit.modules.replaceRackPosition(i, m)
sFit = svcFit.getInstance()
sFit.recalc(fit)
sFit.fill(fit)
# Other stuff
for modRack in (
aFit.rigs,
aFit.services,
aFit.modulesHigh,
aFit.modulesMed,
aFit.modulesLow,
):
for i, m in enumerate(modRack):
if m is None:
dummy = Module.buildEmpty(aFit.getSlotByContainer(modRack))
dummy.owner = fit
fit.modules.replaceRackPosition(i, dummy)
elif m.fits(fit):
m.owner = fit
if not m.isValidState(m.state):
pyfalog.warning('service.port.eft.importEft: module {} cannot have state {}', m, m.state)
fit.modules.replaceRackPosition(i, m)
for implant in aFit.implants:
fit.implants.append(implant)
for booster in aFit.boosters:
fit.boosters.append(booster)
for drone in aFit.drones:
fit.drones.append(drone)
for fighter in aFit.fighters:
fit.fighters.append(fighter)
for cargo in aFit.cargo.values():
fit.cargo.append(cargo)
return fit
def importEftCfg(shipname, lines, progress):
"""Handle import from EFT config store file"""
# Check if we have such ship in database, bail if we don't
sMkt = Market.getInstance()
try:
sMkt.getItem(shipname)
except (KeyboardInterrupt, SystemExit):
raise
except:
return [] # empty list is expected
fits = [] # List for fits
fitIndices = [] # List for starting line numbers for each fit
for line in lines:
# Detect fit header
if line[:1] == "[" and line[-1:] == "]":
# Line index where current fit starts
startPos = lines.index(line)
fitIndices.append(startPos)
for i, startPos in enumerate(fitIndices):
if progress and progress.userCancelled:
return []
# End position is last file line if we're trying to get it for last fit,
# or start position of next fit minus 1
endPos = len(lines) if i == len(fitIndices) - 1 else fitIndices[i + 1]
# Finally, get lines for current fitting
fitLines = lines[startPos:endPos]
try:
# Create fit object
fitobj = Fit()
# Strip square brackets and pull out a fit name
fitobj.name = fitLines[0][1:-1]
# Assign ship to fitting
try:
fitobj.ship = Ship(sMkt.getItem(shipname))
except ValueError:
fitobj.ship = Citadel(sMkt.getItem(shipname))
moduleList = []
for x in range(1, len(fitLines)):
line = fitLines[x]
if not line:
continue
# Parse line into some data we will need
misc = re.match(r"(Drones|Implant|Booster)_(Active|Inactive)=(.+)", line)
cargo = re.match(r"Cargohold=(.+)", line)
# 2017/03/27 NOTE: store description from EFT
description = re.match(r"Description=(.+)", line)
if misc:
entityType = misc.group(1)
entityState = misc.group(2)
entityData = misc.group(3)
if entityType == "Drones":
droneData = re.match(r"(.+),([0-9]+)", entityData)
# Get drone name and attempt to detect drone number
droneName = droneData.group(1) if droneData else entityData
droneAmount = int(droneData.group(2)) if droneData else 1
# Bail if we can't get item or it's not from drone category
try:
droneItem = sMkt.getItem(droneName, eager="group.category")
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
continue
if droneItem.category.name == "Drone":
# Add drone to the fitting
d = Drone(droneItem)
d.amount = droneAmount
if entityState == "Active":
d.amountActive = droneAmount
elif entityState == "Inactive":
d.amountActive = 0
fitobj.drones.append(d)
elif droneItem.category.name == "Fighter": # EFT saves fighter as drones
ft = Fighter(droneItem)
ft.amount = int(droneAmount) if ft.amount <= ft.fighterSquadronMaxSize else ft.fighterSquadronMaxSize
fitobj.fighters.append(ft)
else:
continue
elif entityType == "Implant":
# Bail if we can't get item or it's not from implant category
try:
implantItem = sMkt.getItem(entityData, eager="group.category")
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
continue
if implantItem.category.name != "Implant":
continue
# Add implant to the fitting
imp = Implant(implantItem)
if entityState == "Active":
imp.active = True
elif entityState == "Inactive":
imp.active = False
fitobj.implants.append(imp)
elif entityType == "Booster":
# Bail if we can't get item or it's not from implant category
try:
boosterItem = sMkt.getItem(entityData, eager="group.category")
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
continue
# All boosters have implant category
if boosterItem.category.name != "Implant":
continue
# Add booster to the fitting
b = Booster(boosterItem)
if entityState == "Active":
b.active = True
elif entityState == "Inactive":
b.active = False
fitobj.boosters.append(b)
# If we don't have any prefixes, then it's a module
elif cargo:
cargoData = re.match(r"(.+),([0-9]+)", cargo.group(1))
cargoName = cargoData.group(1) if cargoData else cargo.group(1)
cargoAmount = int(cargoData.group(2)) if cargoData else 1
# Bail if we can't get item
try:
item = sMkt.getItem(cargoName)
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
continue
# Add Cargo to the fitting
c = Cargo(item)
c.amount = cargoAmount
fitobj.cargo.append(c)
# 2017/03/27 NOTE: store description from EFT
elif description:
fitobj.notes = description.group(1).replace("|", "\n")
else:
withCharge = re.match(r"(.+),(.+)", line)
modName = withCharge.group(1) if withCharge else line
chargeName = withCharge.group(2) if withCharge else None
# If we can't get module item, skip it
try:
modItem = sMkt.getItem(modName)
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
continue
# Create module
m = Module(modItem)
# Add subsystems before modules to make sure T3 cruisers have subsystems installed
if modItem.category.name == "Subsystem":
if m.fits(fitobj):
fitobj.modules.append(m)
else:
m.owner = fitobj
# Activate mod if it is activable
if m.isValidState(FittingModuleState.ACTIVE):
m.state = activeStateLimit(m.item)
# Add charge to mod if applicable, on any errors just don't add anything
if chargeName:
try:
chargeItem = sMkt.getItem(chargeName, eager="group.category")
if chargeItem.category.name == "Charge":
m.charge = chargeItem
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning("Cannot get item.")
pass
# Append module to fit
moduleList.append(m)
# Recalc to get slot numbers correct for T3 cruisers
sFit = svcFit.getInstance()
sFit.recalc(fitobj)
sFit.fill(fitobj)
for module in moduleList:
if module.fits(fitobj):
fitobj.modules.append(module)
# Append fit to list of fits
fits.append(fitobj)
if progress:
progress.message = "%s:\n%s" % (fitobj.ship.name, fitobj.name)
except (KeyboardInterrupt, SystemExit):
raise
# Skip fit silently if we get an exception
except Exception as e:
pyfalog.error("Caught exception on fit.")
pyfalog.error(e)
pass
return fits
def _importPrepare(lines):
for i in range(len(lines)):
lines[i] = lines[i].strip()
while lines and not lines[0]:
del lines[0]
while lines and not lines[-1]:
del lines[-1]
return lines
mutantHeaderPattern = re.compile(r'^\[(?P<ref>\d+)\](?P<tail>.*)')
def importGetMutationData(lines):
# Format: {ref: [lines]}
mutaLinesMap = {}
currentMutaRef = None
currentMutaLines = []
consumedIndices = set()
def completeMutaLines():
if currentMutaRef is not None and currentMutaLines:
mutaLinesMap[currentMutaRef] = currentMutaLines
for i, line in enumerate(lines):
m = mutantHeaderPattern.match(line)
# Start and reset at header line
if m:
completeMutaLines()
currentMutaRef = int(m.group('ref'))
currentMutaLines = []
currentMutaLines.append(m.group('tail'))
consumedIndices.add(i)
# Reset at blank line
elif not line:
completeMutaLines()
currentMutaRef = None
currentMutaLines = []
elif currentMutaRef is not None:
currentMutaLines.append(line)
consumedIndices.add(i)
else:
completeMutaLines()
# Clear mutant info from source
for i in sorted(consumedIndices, reverse=True):
del lines[i]
# Run parsing
data = {}
for ref, mutaLines in mutaLinesMap.items():
_, mutaType, mutaAttrs = parseMutant(mutaLines)
data[ref] = (mutaType, mutaAttrs)
return data
def _importSectionIter(lines):
section = Section()
for line in lines:
if not line:
if section.lines:
yield section
section = Section()
else:
section.lines.append(line)
if section.lines:
yield section
def _importCreateFit(lines):
"""Create fit and set top-level entity (ship or citadel)."""
fit = Fit()
header = lines.pop(0)
m = re.match(r'\[(?P<shipType>[^,]+),\s*(?P<fitName>.+)\]', header)
if not m:
pyfalog.warning('service.port.eft.importEft: corrupted fit header')
raise EftImportError
shipType = m.group('shipType').strip()
fitName = m.group('fitName').strip()
try:
ship = fetchItem(shipType)
try:
fit.ship = Ship(ship)
except ValueError:
fit.ship = Citadel(ship)
fit.name = fitName
except (KeyboardInterrupt, SystemExit):
raise
except:
pyfalog.warning('service.port.eft.importEft: exception caught when parsing header')
raise EftImportError
return fit
def _clearTail(lst):
while lst and lst[-1] is None:
del lst[-1]
class EftImportError(Exception):
"""Exception class emitted and consumed by EFT importer internally."""
...
class Section:
def __init__(self):
self.lines = []
self.itemSpecs = []
self.__itemDataCats = None
@property
def itemDataCats(self):
if self.__itemDataCats is None:
cats = set()
for itemSpec in self.itemSpecs:
if itemSpec is None:
continue
cats.add(itemSpec.item.category.name)
self.__itemDataCats = tuple(sorted(cats))
return self.__itemDataCats
@property
def isModuleRack(self):
return all(i is None or i.isModule for i in self.itemSpecs)
@property
def isImplantRack(self):
return all(i is not None and i.isImplant for i in self.itemSpecs)
@property
def isDroneBay(self):
return all(i is not None and i.isDrone for i in self.itemSpecs)
@property
def isFighterBay(self):
return all(i is not None and i.isFighter for i in self.itemSpecs)
@property
def isCargoHold(self):
return (
all(i is not None and i.isCargo for i in self.itemSpecs) and
not self.isDroneBay and not self.isFighterBay)
class BaseItemSpec:
def __init__(self, typeName):
item = fetchItem(typeName, eagerCat=True)
if item is None:
raise EftImportError
self.typeName = typeName
self.item = item
@property
def isModule(self):
return False
@property
def isImplant(self):
return False
@property
def isDrone(self):
return False
@property
def isFighter(self):
return False
@property
def isCargo(self):
return False
class RegularItemSpec(BaseItemSpec):
def __init__(self, typeName, chargeName=None):
super().__init__(typeName)
self.charge = self.__fetchCharge(chargeName)
self.offline = False
self.mutationIdx = None
def __fetchCharge(self, chargeName):
if chargeName:
charge = fetchItem(chargeName, eagerCat=True)
if not charge or charge.category.name != 'Charge':
charge = None
else:
charge = None
return charge
@property
def isModule(self):
return self.item.category.name in MODULE_CATS
@property
def isImplant(self):
return (
self.item.category.name == 'Implant' and (
'implantness' in self.item.attributes or
'boosterness' in self.item.attributes))
class MultiItemSpec(BaseItemSpec):
def __init__(self, typeName):
super().__init__(typeName)
self.amount = 0
self.mutationIdx = None
@property
def isDrone(self):
return self.item.category.name == 'Drone'
@property
def isFighter(self):
return self.item.category.name == 'Fighter'
@property
def isCargo(self):
return True
class AbstractFit:
def __init__(self):
# Modules
self.modulesHigh = []
self.modulesMed = []
self.modulesLow = []
self.rigs = []
self.subsystems = []
self.services = []
# Non-modules
self.implants = []
self.boosters = []
self.drones = []
self.fighters = []
self.cargo = {} # Format: {item: Cargo}
# Other stuff
self.mutations = {} # Format: {reference: (mutaplamid item, {attr ID: attr value})}
@property
def __slotContainerMap(self):
return {
FittingSlot.HIGH: self.modulesHigh,
FittingSlot.MED: self.modulesMed,
FittingSlot.LOW: self.modulesLow,
FittingSlot.RIG: self.rigs,
FittingSlot.SUBSYSTEM: self.subsystems,
FittingSlot.SERVICE: self.services}
def getContainerBySlot(self, slotType):
return self.__slotContainerMap.get(slotType)
def getSlotByContainer(self, container):
slotType = None
for k, v in self.__slotContainerMap.items():
if v is container:
slotType = k
break
return slotType
def addModules(self, itemSpecs):
modules = []
slotTypes = set()
for itemSpec in itemSpecs:
if itemSpec is None:
modules.append(None)
continue
m = self.__makeModule(itemSpec)
if m is None:
modules.append(None)
continue
modules.append(m)
slotTypes.add(m.slot)
_clearTail(modules)
# If all the modules have same slot type, put them to appropriate
# container with stubs
if len(slotTypes) == 1:
slotType = tuple(slotTypes)[0]
self.getContainerBySlot(slotType).extend(modules)
# Otherwise, put just modules
else:
for m in modules:
if m is None:
continue
self.getContainerBySlot(m.slot).append(m)
def addModule(self, itemSpec):
if itemSpec is None:
return
m = self.__makeModule(itemSpec)
if m is not None:
self.getContainerBySlot(m.slot).append(m)
def __makeModule(self, itemSpec):
# Mutate item if needed
m = None
if itemSpec.mutationIdx in self.mutations:
mutaItem, mutaAttrs = self.mutations[itemSpec.mutationIdx]
mutaplasmid = getDynamicItem(mutaItem.ID)
if mutaplasmid:
try:
m = Module(mutaplasmid.resultingItem, itemSpec.item, mutaplasmid)
except ValueError:
pass
else:
for attrID, mutator in m.mutators.items():
if attrID in mutaAttrs:
mutator.value = mutaAttrs[attrID]
# If we still don't have item (item is not mutated or we
# failed to construct mutated item), try to make regular item
if m is None:
try:
m = Module(itemSpec.item)
except ValueError:
return None
if itemSpec.charge is not None and m.isValidCharge(itemSpec.charge):
m.charge = itemSpec.charge
if itemSpec.offline and m.isValidState(FittingModuleState.OFFLINE):
m.state = FittingModuleState.OFFLINE
elif m.isValidState(FittingModuleState.ACTIVE):
m.state = activeStateLimit(m.item)
return m
def addImplant(self, itemSpec):
if itemSpec is None:
return
if 'implantness' in itemSpec.item.attributes:
self.implants.append(Implant(itemSpec.item))
elif 'boosterness' in itemSpec.item.attributes:
self.boosters.append(Booster(itemSpec.item))
else:
pyfalog.error('Failed to import implant: {}', itemSpec.typeName)
def addDrone(self, itemSpec):
if itemSpec is None:
return
drone = None
if itemSpec.mutationIdx in self.mutations:
mutaItem, mutaAttrs = self.mutations[itemSpec.mutationIdx]
mutaplasmid = getDynamicItem(mutaItem.ID)
if mutaplasmid:
try:
drone = Drone(mutaplasmid.resultingItem, itemSpec.item, mutaplasmid)
except ValueError:
pass
else:
for attrID, mutator in drone.mutators.items():
if attrID in mutaAttrs:
mutator.value = mutaAttrs[attrID]
if drone is None:
try:
drone = Drone(itemSpec.item)
except ValueError:
return
drone.amount = itemSpec.amount
if drone.isMutated:
self.drones.append(drone)
else:
for fitDrone in self.drones:
if fitDrone.item.ID == itemSpec.item.ID:
fitDrone.amount += drone.amount
break
else:
self.drones.append(drone)
def addFighter(self, itemSpec):
if itemSpec is None:
return
fighter = Fighter(itemSpec.item)
fighter.amount = itemSpec.amount
self.fighters.append(fighter)
def addCargo(self, itemSpec):
if itemSpec is None:
return
if itemSpec.item not in self.cargo:
self.cargo[itemSpec.item] = Cargo(itemSpec.item)
self.cargo[itemSpec.item].amount += itemSpec.amount
def lineIter(text):
"""Iterate over non-blank lines."""
for line in text.splitlines():
line = line.strip()
if line:
yield line
def parseAdditions(text, mutaData=None):
items = []
sMkt = Market.getInstance()
pattern = r'^(?P<typeName>{}+?)( x(?P<amount>\d+?))?(\s*\[(?P<mutaref>\d+?)\])?$'.format(NAME_CHARS)
for line in lineIter(text):
m = re.match(pattern, line)
if not m:
continue
item = sMkt.getItem(m.group('typeName'))
if item is None:
continue
amount = m.group('amount')
amount = 1 if amount is None else int(amount)
mutaRef = int(m.group('mutaref')) if m.group('mutaref') else None
if mutaRef and mutaData and mutaRef in mutaData:
mutation = mutaData[mutaRef]
else:
mutation = None
items.append((item, amount, mutation))
return items
def isValidDroneImport(text):
lines = list(lineIter(text))
mutaData = importGetMutationData(lines)
text = '\n'.join(lines)
pattern = r'x\d+(\s*\[\d+\])?$'
for line in lineIter(text):
if not re.search(pattern, line):
return False, ()
itemData = parseAdditions(text, mutaData=mutaData)
if not itemData:
return False, ()
for item, amount, mutation in itemData:
if not item.isDrone:
return False, ()
return True, itemData
def isValidFighterImport(text):
pattern = r'x\d+$'
for line in lineIter(text):
if not re.search(pattern, line):
return False, ()
itemData = parseAdditions(text)
if not itemData:
return False, ()
for item, amount, mutation in itemData:
if not item.isFighter:
return False, ()
return True, itemData
def isValidCargoImport(text):
pattern = r'x\d+$'
for line in lineIter(text):
if not re.search(pattern, line):
return False, ()
itemData = parseAdditions(text)
if not itemData:
return False, ()
for item, amount, mutation in itemData:
if item.isAbyssal:
return False, ()
return True, itemData
def isValidImplantImport(text):
pattern = r'x\d+$'
for line in lineIter(text):
if re.search(pattern, line):
return False, ()
itemData = parseAdditions(text)
if not itemData:
return False, ()
for item, amount, mutation in itemData:
if not item.isImplant:
return False, ()
return True, itemData
def isValidBoosterImport(text):
pattern = r'x\d+$'
for line in lineIter(text):
if re.search(pattern, line):
return False, ()
itemData = parseAdditions(text)
if not itemData:
return False, ()
for item, amount, mutation in itemData:
if not item.isBooster:
return False, ()
return True, itemData