Merge pull request #1803 from pyfa-org/muta_item_import

Mutated item import
This commit is contained in:
Ryan Holmes
2018-11-20 20:44:57 -05:00
committed by GitHub
9 changed files with 311 additions and 96 deletions

View File

@@ -131,7 +131,7 @@ class EveFittings(wx.Frame):
return
data = self.fitTree.fittingsTreeCtrl.GetItemData(selection)
sPort = Port.getInstance()
fits = sPort.importFitFromBuffer(data)
import_type, fits = sPort.importFitFromBuffer(data)
self.mainFrame._openAfterImport(fits)
def deleteFitting(self, event):

View File

@@ -32,4 +32,5 @@ from .guiChangeDroneQty import GuiChangeDroneQty
from .guiChangeProjectedDroneQty import GuiChangeProjectedDroneQty
from .guiToggleDrone import GuiToggleDroneCommand
from .guiFitRename import GuiFitRenameCommand
from .guiChangeImplantLocation import GuiChangeImplantLocation
from .guiChangeImplantLocation import GuiChangeImplantLocation
from .guiImportMutatedModule import GuiImportMutatedModuleCommand

View File

@@ -0,0 +1,93 @@
import wx
from eos.saveddata.module import Module, State
import eos.db
from eos.db.gamedata.queries import getDynamicItem
from logbook import Logger
from service.fit import Fit
pyfalog = Logger(__name__)
class FitImportMutatedCommand(wx.Command):
""""
Fitting command that takes info about mutated module, composes it and adds it to a fit
"""
def __init__(self, fitID, baseItem, mutaItem, attrMap):
wx.Command.__init__(self, True)
self.fitID = fitID
self.baseItem = baseItem
self.mutaItem = mutaItem
self.attrMap = attrMap
self.new_position = None
self.change = None
self.replace_cmd = None
def Do(self):
sFit = Fit.getInstance()
fitID = self.fitID
fit = eos.db.getFit(fitID)
if self.baseItem is None:
pyfalog.warning("Unable to build non-mutated module: no base item to build from")
return False
try:
mutaTypeID = self.mutaItem.ID
except AttributeError:
mutaplasmid = None
else:
mutaplasmid = getDynamicItem(mutaTypeID)
# Try to build simple item even though no mutaplasmid found
if mutaplasmid is None:
try:
module = Module(self.baseItem)
except ValueError:
pyfalog.warning("Unable to build non-mutated module: {}", self.baseItem)
return False
# Build mutated module otherwise
else:
try:
module = Module(mutaplasmid.resultingItem, self.baseItem, mutaplasmid)
except ValueError:
pyfalog.warning("Unable to build mutated module: {} {}", self.baseItem, self.mutaItem)
return False
else:
for attrID, mutator in module.mutators.items():
if attrID in self.attrMap:
mutator.value = self.attrMap[attrID]
# this is essentially the same as the FitAddModule command. possibly look into centralizing this functionality somewhere?
if module.fits(fit):
pyfalog.debug("Adding {} as module for fit {}", module, fit)
module.owner = fit
numSlots = len(fit.modules)
fit.modules.append(module)
if module.isValidState(State.ACTIVE):
module.state = State.ACTIVE
# todo: fix these
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
# self.recalc(fit)
# Then, check states of all modules and change where needed. This will recalc if needed
sFit.checkStates(fit, module)
# fit.fill()
eos.db.commit()
self.change = numSlots != len(fit.modules)
self.new_position = module.modPosition
else:
return False
return True
def Undo(self):
# We added a subsystem module, which actually ran the replace command. Run the undo for that guy instead
if self.replace_cmd:
return self.replace_cmd.Undo()
from .fitRemoveModule import FitRemoveModuleCommand # Avoid circular import
if self.new_position is not None:
cmd = FitRemoveModuleCommand(self.fitID, [self.new_position])
cmd.Do()
return True

View File

@@ -0,0 +1,38 @@
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from .calc.fitImportMutatedModule import FitImportMutatedCommand
from service.fit import Fit
from logbook import Logger
pyfalog = Logger(__name__)
class GuiImportMutatedModuleCommand(wx.Command):
def __init__(self, fitID, baseItem, mutaItem, attrMap):
wx.Command.__init__(self, True, "Mutated Module Import: {} {} {}".format(baseItem, mutaItem, attrMap))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.sFit = Fit.getInstance()
self.fitID = fitID
self.baseItem = baseItem
self.mutaItem = mutaItem
self.attrMap = attrMap
self.internal_history = wx.CommandProcessor()
def Do(self):
pyfalog.debug("{} Do()".format(self))
if self.internal_history.Submit(FitImportMutatedCommand(self.fitID, self.baseItem, self.mutaItem, self.attrMap)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="modadd"))
return True
return False
def Undo(self):
pyfalog.debug("{} Undo()".format(self))
for _ in self.internal_history.Commands:
self.internal_history.Undo()
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="moddel"))
return True

View File

@@ -73,6 +73,7 @@ from service.fit import Fit
from service.port import EfsPort, IPortUser, Port
from service.settings import HTMLExportSettings, SettingsProvider
from service.update import Update
import gui.fitCommands as cmd
disableOverrideEditor = False
@@ -728,12 +729,18 @@ class MainFrame(wx.Frame):
def importFromClipboard(self, event):
clipboard = fromClipboard()
activeFit = self.getActiveFit()
try:
fits = Port().importFitFromBuffer(clipboard, self.getActiveFit())
importType, importData = Port().importFitFromBuffer(clipboard, activeFit)
# If it's mutated item - make sure there's at least base item specified
if importType == "MutatedItem":
# we've imported an Abyssal module, need to fire off the command to add it to the fit
self.command.Submit(cmd.GuiImportMutatedModuleCommand(activeFit, *importData[0]))
return # no need to do anything else
except:
pyfalog.error("Attempt to import failed:\n{0}", clipboard)
else:
self._openAfterImport(fits)
self._openAfterImport(importData)
def exportToClipboard(self, event):
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,

View File

@@ -22,7 +22,7 @@ import re
from logbook import Logger
from eos.db.gamedata.queries import getAttributeInfo, getDynamicItem
from eos.db.gamedata.queries import getDynamicItem
from eos.saveddata.cargo import Cargo
from eos.saveddata.citadel import Citadel
from eos.saveddata.booster import Booster
@@ -32,10 +32,10 @@ from eos.saveddata.implant import Implant
from eos.saveddata.module import Module, State, Slot
from eos.saveddata.ship import Ship
from eos.saveddata.fit import Fit
from gui.utils.numberFormatter import roundToPrec
from service.fit import Fit as svcFit
from service.market import Market
from service.port.shared import IPortUser, processing_notify
from service.port.muta import parseMutant, renderMutant
from service.port.shared import IPortUser, fetchItem, processing_notify
from enum import Enum
@@ -157,17 +157,7 @@ def exportEft(fit, options):
if mutants and options & Options.MUTATIONS.value:
for mutantReference in sorted(mutants):
mutant = mutants[mutantReference]
mutatedAttrs = {}
for attrID, mutator in mutant.mutators.items():
attrName = getAttributeInfo(attrID).name
mutatedAttrs[attrName] = mutator.value
mutationLines.append('[{}] {}'.format(mutantReference, mutant.baseItem.name))
mutationLines.append(' {}'.format(mutant.mutaplasmid.item.name))
# Round to 7th significant number to avoid exporting float errors
customAttrsLine = ', '.join(
'{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7))
for a in sorted(mutatedAttrs))
mutationLines.append(' {}'.format(customAttrsLine))
mutationLines.append(renderMutant(mutant, firstPrefix='[{}] '.format(mutantReference), prefix=' '))
if mutationLines:
sections.append('\n'.join(mutationLines))
@@ -507,59 +497,48 @@ def _importPrepareString(eftString):
return lines
mutantHeaderPattern = re.compile('^\[(?P<ref>\d+)\](?P<tail>.*)')
def _importGetMutationData(lines):
data = {}
# Format: {ref: [lines]}
mutaLinesMap = {}
currentMutaRef = None
currentMutaLines = []
consumedIndices = set()
for i in range(len(lines)):
line = lines[i]
m = re.match('^\[(?P<ref>\d+)\]', line)
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:
ref = int(m.group('ref'))
# Attempt to apply mutation is useless w/o mutaplasmid, so skip it
# altogether if we have no info on it
try:
mutaName = lines[i + 1]
except IndexError:
continue
else:
consumedIndices.add(i)
consumedIndices.add(i + 1)
# Get custom attribute values
mutaAttrs = {}
try:
mutaAttrsLine = lines[i + 2]
except IndexError:
pass
else:
consumedIndices.add(i + 2)
pairs = [p.strip() for p in mutaAttrsLine.split(',')]
for pair in pairs:
try:
attrName, value = pair.split(' ')
except ValueError:
continue
try:
value = float(value)
except (ValueError, TypeError):
continue
attrInfo = getAttributeInfo(attrName.strip())
if attrInfo is None:
continue
mutaAttrs[attrInfo.ID] = value
mutaItem = _fetchItem(mutaName)
if mutaItem is None:
continue
data[ref] = (mutaItem, mutaAttrs)
# If we got here, we have seen at least correct reference line and
# mutaplasmid name line
i += 2
# Bonus points for seeing correct attrs line. Worst case we
# will have to scan it once again
if mutaAttrs:
i += 1
# Cleanup the lines from mutaplasmid info
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
@@ -587,7 +566,7 @@ def _importCreateFit(lines):
shipType = m.group('shipType').strip()
fitName = m.group('fitName').strip()
try:
ship = _fetchItem(shipType)
ship = fetchItem(shipType)
try:
fit.ship = Ship(ship)
except ValueError:
@@ -599,20 +578,6 @@ def _importCreateFit(lines):
return fit
def _fetchItem(typeName, eagerCat=False):
sMkt = Market.getInstance()
eager = 'group.category' if eagerCat else None
try:
item = sMkt.getItem(typeName, eager=eager)
except:
pyfalog.warning('service.port.eft: unable to fetch item "{}"'.format(typeName))
return None
if sMkt.getPublicityByItem(item):
return item
else:
return None
def _clearTail(lst):
while lst and lst[-1] is None:
del lst[-1]
@@ -667,7 +632,7 @@ class Section:
class BaseItemSpec:
def __init__(self, typeName):
item = _fetchItem(typeName, eagerCat=True)
item = fetchItem(typeName, eagerCat=True)
if item is None:
raise EftImportError
self.typeName = typeName
@@ -704,7 +669,7 @@ class RegularItemSpec(BaseItemSpec):
def __fetchCharge(self, chargeName):
if chargeName:
charge = _fetchItem(chargeName, eagerCat=True)
charge = fetchItem(chargeName, eagerCat=True)
if not charge or charge.category.name != 'Charge':
charge = None
else:

79
service/port/muta.py Normal file
View File

@@ -0,0 +1,79 @@
# =============================================================================
# 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/>.
# =============================================================================
from eos.db.gamedata.queries import getAttributeInfo
from gui.utils.numberFormatter import roundToPrec
from service.port.shared import fetchItem
def renderMutant(mutant, firstPrefix='', prefix=''):
exportLines = []
mutatedAttrs = {}
for attrID, mutator in mutant.mutators.items():
attrName = getAttributeInfo(attrID).name
mutatedAttrs[attrName] = mutator.value
exportLines.append('{}{}'.format(firstPrefix, mutant.baseItem.name))
exportLines.append('{}{}'.format(prefix, mutant.mutaplasmid.item.name))
# Round to 7th significant number to avoid exporting float errors
customAttrsLine = ', '.join(
'{} {}'.format(a, roundToPrec(mutatedAttrs[a], 7))
for a in sorted(mutatedAttrs))
exportLines.append('{}{}'.format(prefix, customAttrsLine))
return '\n'.join(exportLines)
def parseMutant(lines):
# Fetch base item type
try:
baseName = lines[0]
except IndexError:
return None
baseType = fetchItem(baseName.strip())
if baseType is None:
return None, None, {}
# Fetch mutaplasmid item type and actual item
try:
mutaName = lines[1]
except IndexError:
return baseType, None, {}
mutaType = fetchItem(mutaName.strip())
if mutaType is None:
return baseType, None, {}
# Process mutated attribute values
try:
mutaAttrsLine = lines[2]
except IndexError:
return baseType, mutaType, {}
mutaAttrs = {}
pairs = [p.strip() for p in mutaAttrsLine.split(',')]
for pair in pairs:
try:
attrName, value = pair.split(' ')
except ValueError:
continue
try:
value = float(value)
except (ValueError, TypeError):
continue
attrInfo = getAttributeInfo(attrName.strip())
if attrInfo is None:
continue
mutaAttrs[attrInfo.ID] = value
return baseType, mutaType, mutaAttrs

View File

@@ -37,6 +37,7 @@ from service.port.esi import exportESI, importESI
from service.port.multibuy import exportMultiBuy
from service.port.shared import IPortUser, UserCancelException, processing_notify
from service.port.xml import importXml, exportXml
from service.port.muta import parseMutant
pyfalog = Logger(__name__)
@@ -188,18 +189,20 @@ class Port(object):
# TODO: catch the exception?
# activeFit is reserved?, bufferStr is unicode? (assume only clipboard string?
sFit = svcFit.getInstance()
_, fits = Port.importAuto(bufferStr, activeFit=activeFit)
for fit in fits:
fit.character = sFit.character
fit.damagePattern = sFit.pattern
fit.targetResists = sFit.targetResists
if len(fit.implants) > 0:
fit.implantLocation = ImplantLocation.FIT
else:
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
db.save(fit)
return fits
importType, importData = Port.importAuto(bufferStr, activeFit=activeFit)
if importType != "MutatedItem":
for fit in importData:
fit.character = sFit.character
fit.damagePattern = sFit.pattern
fit.targetResists = sFit.targetResists
if len(fit.implants) > 0:
fit.implantLocation = ImplantLocation.FIT
else:
useCharImplants = sFit.serviceFittingOptions["useCharacterImplantsByDefault"]
fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT
db.save(fit)
return importType, importData
@classmethod
def importAuto(cls, string, path=None, activeFit=None, iportuser=None):
@@ -228,8 +231,16 @@ class Port(object):
if re.match("\[.*,.*\]", firstLine):
return "EFT", (cls.importEft(string),)
# Use DNA format for all other cases
return "DNA", (cls.importDna(string),)
# Check if string is in DNA format
if re.match("\d+(:\d+(;\d+))*::", firstLine):
return "DNA", (cls.importDna(string),)
# Assume that we import stand-alone abyssal module if all else fails
try:
return "MutatedItem", (parseMutant(string.split("\n")),)
except:
pass
# EFT-related methods
@staticmethod

View File

@@ -20,6 +20,13 @@
from abc import ABCMeta, abstractmethod
from logbook import Logger
from service.market import Market
pyfalog = Logger(__name__)
class UserCancelException(Exception):
"""when user cancel on port processing."""
@@ -68,3 +75,17 @@ class IPortUser(metaclass=ABCMeta):
def processing_notify(iportuser, flag, data):
if not iportuser.on_port_processing(flag, data):
raise UserCancelException
def fetchItem(typeName, eagerCat=False):
sMkt = Market.getInstance()
eager = 'group.category' if eagerCat else None
try:
item = sMkt.getItem(typeName, eager=eager)
except:
pyfalog.warning('service.port.shared: unable to fetch item "{}"'.format(typeName))
return None
if sMkt.getPublicityByItem(item):
return item
else:
return None