# =============================================================================== # Copyright (C) 2010 Diego Duclos # # 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 . # =============================================================================== import copy import datetime from time import time import wx from logbook import Logger import eos.db from eos.const import FittingModuleState, ImplantLocation from eos.saveddata.character import Character as saveddata_Character from eos.saveddata.citadel import Citadel as es_Citadel from eos.saveddata.damagePattern import DamagePattern as es_DamagePattern from eos.saveddata.fit import Fit as FitType from eos.saveddata.ship import Ship as es_Ship from service.character import Character from service.damagePattern import DamagePattern from service.settings import SettingsProvider pyfalog = Logger(__name__) class DeferRecalc: def __init__(self, fitID): self.fitID = fitID self.sFit = Fit.getInstance() def __enter__(self): self._recalc = self.sFit.recalc self.sFit.recalc = lambda x: pyfalog.debug('Deferred Recalc') def __exit__(self, *args): self.sFit.recalc = self._recalc self.sFit.recalc(self.fitID) # inherits from FitDeprecated so that I can move all the dead shit, but not affect functionality class Fit: instance = None processors = {} @classmethod def getInstance(cls): if cls.instance is None: cls.instance = Fit() return cls.instance def __init__(self): pyfalog.debug("Initialize Fit class") self.pattern = DamagePattern.getInstance().getDamagePattern("Uniform") self.targetResists = None self.character = saveddata_Character.getAll5() self.booster = False self.dirtyFitIDs = set() serviceFittingDefaultOptions = { "useGlobalCharacter": False, "useCharacterImplantsByDefault": True, "useGlobalDamagePattern": False, "defaultCharacter": self.character.ID, "useGlobalForceReload": False, "colorFitBySlot": False, "rackSlots": True, "rackLabels": True, "compactSkills": True, "showTooltip": True, "showMarketShortcuts": False, "enableGaugeAnimation": True, "openFitInNew": False, "priceSystem": "Jita", "priceSource": "eve-marketdata.com", "showShipBrowserTooltip": True, "marketSearchDelay": 250, "ammoChangeAll": False, } self.serviceFittingOptions = SettingsProvider.getInstance().getSettings( "pyfaServiceFittingOptions", serviceFittingDefaultOptions) @staticmethod def getAllFits(): pyfalog.debug("Fetching all fits") fits = eos.db.getFitList() return fits @staticmethod def getFitsWithShip(shipID): """ Lists fits of shipID, used with shipBrowser """ pyfalog.debug("Fetching all fits for ship ID: {0}", shipID) fits = eos.db.getFitsWithShip(shipID) names = [] for fit in fits: names.append((fit.ID, fit.name, fit.booster, fit.modified or fit.created or datetime.datetime.fromtimestamp(fit.timestamp), fit.notes, fit.ship.item.graphicID)) return names @staticmethod def getRecentFits(): """ Fetches recently modified fits, used with shipBrowser """ pyfalog.debug("Fetching recent fits") fits = eos.db.getRecentFits() returnInfo = [] for fit in fits: item = eos.db.getItem(fit[1]) returnInfo.append((fit[0], fit[2], fit[3] or fit[4] or datetime.datetime.fromtimestamp(fit[5]), item, fit[6])) # ID name timestamps item notes return returnInfo @staticmethod def getFitsWithModules(typeIDs): """ Lists fits flagged as booster """ fits = eos.db.getFitsWithModules(typeIDs) return fits @staticmethod def countAllFits(): pyfalog.debug("Getting count of all fits.") return eos.db.countAllFits() @staticmethod def countAllFitsGroupedByShip(): count = eos.db.countFitGroupedByShip() return count @staticmethod def countFitsWithShip(stuff): pyfalog.debug("Getting count of all fits for: {0}", stuff) count = eos.db.countFitsWithShip(stuff) return count @staticmethod def getModule(fitID, pos): fit = eos.db.getFit(fitID) return fit.modules[pos] def newFit(self, shipID, name=None): pyfalog.debug("Creating new fit for ID: {0}", shipID) try: ship = es_Ship(eos.db.getItem(shipID)) except ValueError: ship = es_Citadel(eos.db.getItem(shipID)) fit = FitType(ship) fit.name = name if name is not None else "New %s" % fit.ship.item.name fit.damagePattern = self.pattern fit.targetResists = self.targetResists fit.character = self.character fit.booster = self.booster useCharImplants = self.serviceFittingOptions["useCharacterImplantsByDefault"] fit.implantLocation = ImplantLocation.CHARACTER if useCharImplants else ImplantLocation.FIT eos.db.save(fit) self.recalc(fit) self.fill(fit) return fit.ID @staticmethod def toggleBoostFit(fitID): pyfalog.debug("Toggling as booster for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) fit.booster = not fit.booster eos.db.commit() @staticmethod def deleteFit(fitID): fit = eos.db.getFit(fitID) pyfalog.debug("Fit::deleteFit - Deleting fit: {}", fit) # refresh any fits this fit is projected onto. Otherwise, if we have # already loaded those fits, they will not reflect the changes # A note on refreshFits: we collect the target fits in a set because # if a target fit has the same fit for both projected and command, # it will be refreshed first during the projected loop and throw an # error during the command loop refreshFits = set() for projection in list(fit.projectedOnto.values()): if projection.victim_fit and projection.victim_fit != fit and projection.victim_fit in eos.db.saveddata_session: # GH issue #359 refreshFits.add(projection.victim_fit) for booster in list(fit.boostedOnto.values()): if booster.boosted_fit and booster.boosted_fit != fit and booster.boosted_fit in eos.db.saveddata_session: # GH issue #359 refreshFits.add(booster.boosted_fit) eos.db.remove(fit) if fitID in Fit.processors: del Fit.processors[fitID] pyfalog.debug(" Need to refresh {} fits: {}", len(refreshFits), refreshFits) for fit in refreshFits: eos.db.saveddata_session.refresh(fit) eos.db.saveddata_session.commit() @classmethod def getCommandProcessor(cls, fitID): if fitID not in cls.processors: cls.processors[fitID] = wx.CommandProcessor(maxCommands=100) return cls.processors[fitID] @staticmethod def copyFit(fitID): pyfalog.debug("Creating copy of fit ID: {0}", fitID) fit = eos.db.getFit(fitID) newFit = copy.deepcopy(fit) eos.db.save(newFit) return newFit.ID @staticmethod def clearFit(fitID): pyfalog.debug("Clearing fit for fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) fit.clear() return fit @staticmethod def editNotes(fitID, notes): fit = eos.db.getFit(fitID) if fit: fit.notes = notes eos.db.commit() def toggleFactorReload(self, fitID): pyfalog.debug("Toggling factor reload for fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) fit.factorReload = not fit.factorReload eos.db.commit() self.recalc(fit) def switchFit(self, fitID): pyfalog.debug("Switching fit to fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) if self.serviceFittingOptions["useGlobalCharacter"]: if fit.character != self.character: fit.calculated = False fit.character = self.character if self.serviceFittingOptions["useGlobalDamagePattern"]: if fit.damagePattern != self.pattern: fit.calculated = False fit.damagePattern = self.pattern eos.db.commit() if not fit.calculated: self.recalc(fit) self.fill(fit) def getFit(self, fitID, projected=False, basic=False): """ Gets fit from database Projected is a recursion flag that is set to reduce recursions into projected fits Basic is a flag to simply return the fit without any other processing """ # pyfalog.debug("Getting fit for fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) if fit is None: return None if basic: return fit inited = getattr(fit, "inited", None) if inited is None or inited is False: if not projected: for fitP in fit.projectedFits: self.getFit(fitP.ID, projected=True) self.recalc(fit) self.fill(fit) # this will loop through modules and set their restriction flag (set in m.fit()) if fit.ignoreRestrictions: for mod in fit.modules: if not mod.isEmpty: mod.fits(fit) # Check that the states of all modules are valid self.checkStates(fit, None) eos.db.commit() fit.inited = True return fit @staticmethod def searchFits(name): pyfalog.debug("Searching for fit: {0}", name) results = eos.db.searchFits(name) fits = [] for fit in sorted(results, key=lambda f: (f.ship.item.group.name, f.ship.item.name, f.name)): fits.append(( fit.ID, fit.name, fit.ship.item.ID, fit.ship.item.name, fit.booster, fit.modifiedCoalesce, fit.notes)) return fits def changeMutatedValuePrelim(self, mutator, value): pyfalog.debug("Changing mutated value for {} / {}: {} => {}".format(mutator.module, mutator.module.mutaplasmid, mutator.value, value)) if mutator.value != value: mutator.value = value eos.db.flush() return mutator.value def changeChar(self, fitID, charID): pyfalog.debug("Changing character ({0}) for fit ID: {1}", charID, fitID) if fitID is None or charID is None: if charID is not None: self.character = Character.getInstance().all5() return fit = eos.db.getFit(fitID) fit.character = self.character = eos.db.getCharacter(charID) self.recalc(fit) self.fill(fit) @staticmethod def getTargetResists(fitID): pyfalog.debug("Get target resists for fit ID: {0}", fitID) if fitID is None: return fit = eos.db.getFit(fitID) return fit.targetResists def setTargetResists(self, fitID, pattern): pyfalog.debug("Set target resist for fit ID: {0}", fitID) if fitID is None: return fit = eos.db.getFit(fitID) fit.targetResists = pattern eos.db.commit() self.recalc(fit) @staticmethod def getDamagePattern(fitID): pyfalog.debug("Get damage pattern for fit ID: {0}", fitID) if fitID is None: return fit = eos.db.getFit(fitID) return fit.damagePattern def setDamagePattern(self, fitID, pattern): pyfalog.debug("Set damage pattern for fit ID: {0}", fitID) if fitID is None: return fit = eos.db.getFit(fitID) fit.damagePattern = self.pattern = pattern eos.db.commit() self.recalc(fit) def setAsPattern(self, fitID, ammo): pyfalog.debug("Set as pattern for fit ID: {0}", fitID) if fitID is None: return sDP = DamagePattern.getInstance() dp = sDP.getDamagePattern("Selected Ammo") if dp is None: dp = es_DamagePattern() dp.name = "Selected Ammo" fit = eos.db.getFit(fitID) for attr in ("em", "thermal", "kinetic", "explosive"): setattr(dp, "%sAmount" % attr, ammo.getAttribute("%sDamage" % attr) or 0) fit.damagePattern = dp self.recalc(fit) def checkStates(self, fit, base): pyfalog.debug("Check states for fit ID: {0}", fit) changedMods = {} changedProjMods = {} changedProjDrones = {} for pos, mod in enumerate(fit.modules): if mod is not base: # fix for #529, where a module may be in incorrect state after CCP changes mechanics of module if not mod.canHaveState(mod.state) or not mod.isValidState(mod.state): changedMods[pos] = mod.state mod.state = FittingModuleState.ONLINE for pos, mod in enumerate(fit.projectedModules): # fix for #529, where a module may be in incorrect state after CCP changes mechanics of module if not mod.canHaveState(mod.state, fit) or not mod.isValidState(mod.state): changedProjMods[pos] = mod.state mod.state = FittingModuleState.OFFLINE for pos, drone in enumerate(fit.projectedDrones): if drone.amountActive > 0 and not drone.canBeApplied(fit): changedProjDrones[pos] = drone.amountActive drone.amountActive = 0 return changedMods, changedProjMods, changedProjDrones @classmethod def fitObjectIter(cls, fit, forceFitImplants=False): yield fit.ship for mod in fit.modules: if not mod.isEmpty: yield mod implants = fit.implants if forceFitImplants else fit.appliedImplants for container in (fit.drones, fit.fighters, implants, fit.boosters, fit.cargo): for obj in container: yield obj @classmethod def fitItemIter(cls, fit, forceFitImplants=False): for fitobj in cls.fitObjectIter(fit, forceFitImplants): yield fitobj.item charge = getattr(fitobj, 'charge', None) if charge: yield charge def refreshFit(self, fitID): pyfalog.debug("Refresh fit for fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) eos.db.commit() self.recalc(fit) self.fill(fit) def recalc(self, fit): if isinstance(fit, int): fit = self.getFit(fit) start_time = time() pyfalog.info("=" * 10 + "recalc: {0}" + "=" * 10, fit.name) fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] fit.clear() fit.calculateModifiedAttributes() pyfalog.info("=" * 10 + "recalc time: " + str(time() - start_time) + "=" * 10) def fill(self, fit): if isinstance(fit, int): fit = self.getFit(fit) return fit.fill()