Merge pull request #1803 from pyfa-org/muta_item_import
Mutated item import
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
93
gui/fitCommands/calc/fitImportMutatedModule.py
Normal file
93
gui/fitCommands/calc/fitImportMutatedModule.py
Normal 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
|
||||
38
gui/fitCommands/guiImportMutatedModule.py
Normal file
38
gui/fitCommands/guiImportMutatedModule.py
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
79
service/port/muta.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user