NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

fittingSvc.py (56246B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\environment\fittingSvc.py
      2 import sys
      3 from collections import defaultdict, Counter
      4 import blue
      5 import dogma.data
      6 import evetypes
      7 import inventorycommon
      8 import inventorycommon.const as invConst
      9 import localization
     10 import log
     11 import uthread
     12 import utillib
     13 from carbon.common.script.sys.service import Service
     14 from carbon.common.script.sys.serviceConst import SERVICE_RUNNING
     15 from carbon.common.script.util.format import FmtAmt
     16 from carbon.common.script.util.commonutils import StripTags
     17 from carbon.common.script.util.linkUtil import GetShowInfoLink
     18 from eve.client.script.ui.control.entries.fitting import FittingModuleEntry
     19 from eve.client.script.ui.control.entries.header import Header
     20 from eve.client.script.ui.control.entries.util import GetFromClass
     21 from eve.client.script.ui.shared.fitting.fighters_util import GetFighterNumByTypeIDsInTubes, GetFightersInTubes
     22 from eve.client.script.ui.shared.fittingScreen.fittingSearchUtil import SearchFittingHelper
     23 from eve.client.script.ui.shared.fittingMgmtWindow import FittingMgmt, ViewFitting
     24 from eve.client.script.ui.shared.fittingScreen.missingItemsPopup import OpenBuyAllBox
     25 from eve.common.lib.appConst import COMMUNITY_FITTING_CORP
     26 from eve.common.script.sys import eveCfg
     27 from eve.common.script.sys.eveCfg import GetActiveShip, InShipInSpace
     28 from eveexceptions import UserError
     29 from evetypes import TYPE_LIST_FILAMENTS, TYPE_LIST_OTHER_CARGO_HOLD_TYPES
     30 from globalConfig.getFunctions import AreCommunityFittingsEnabled
     31 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     32 from shipfitting import Fitting, INVALID_FITTING_ID
     33 from inventorycommon.util import IsShipFittingFlag, IsShipFittable, IsSubsystemFlagVisible, IsFighterTubeFlag, IsModularShip
     34 from carbonui import uiconst
     35 from carbonui.util.various_unsorted import SortListOfTuples, GetClipboardData
     36 from shipfitting.fittingStuff import IsThereRigMismatch
     37 from shipfitting.importFittingUtil import FindShipAndFittingName, ImportFittingUtil
     38 from shipfitting.exportFittingUtil import GetFittingEFTString
     39 from textImporting import IsUsingDefaultLanguage
     40 from utillib import KeyVal
     41 from carbonui.uicore import uicore
     42 from eve.common.lib import appConst as const
     43 CARGO_MARKER = '_'
     44 
     45 class fittingSvc(Service):
     46     __guid__ = 'svc.fittingSvc'
     47     __exportedcalls__ = {'GetFittingDictForActiveShip': [],
     48      'IsTypeFittedInActiveShip': []}
     49     __startupdependencies__ = ['settings', 'invCache']
     50     __notifyevents__ = ['OnSkillsChanged',
     51      'OnFittingAdded',
     52      'OnFittingDeleted',
     53      'OnManyFittingsDeleted',
     54      'OnFittingsUpdated',
     55      'OnGlobalConfigChanged',
     56      'OnCommunityFittingsUpdated',
     57      'OnSessionReset']
     58 
     59     def __init__(self):
     60         Service.__init__(self)
     61         self.Initialize()
     62         self.InitSearchFittingHelper()
     63 
     64     def Initialize(self):
     65         self.fittings = {}
     66         self.hasSkillByFittingID = {}
     67         self.importFittingUtil = None
     68         self.inSimulation = False
     69         self.busyFitting = False
     70 
     71     def Run(self, ms = None):
     72         self.state = SERVICE_RUNNING
     73         self.fittings = {}
     74 
     75     def OnSessionReset(self):
     76         self.Initialize()
     77         self.searchFittingHelper.ResetAllVariables()
     78 
     79     def InitSearchFittingHelper(self):
     80         self.searchFittingHelper = SearchFittingHelper(cfg)
     81         uthread.new(self.searchFittingHelper.BuildNameDict)
     82 
     83     def GetFittingMgr(self, ownerID):
     84         if ownerID == session.charid:
     85             return sm.RemoteSvc('charFittingMgr')
     86         if ownerID == session.corpid:
     87             return sm.RemoteSvc('corpFittingMgr')
     88         if session.allianceid and ownerID == session.allianceid:
     89             return sm.RemoteSvc('allianceFittingMgr')
     90         raise RuntimeError("Can't find the fitting manager you're asking me to get. ownerID:", ownerID)
     91 
     92     def HasLegacyClientFittings(self):
     93         if len(settings.char.generic.Get('fittings', {})) > 0:
     94             return True
     95         return False
     96 
     97     def GetLegacyClientFittings(self):
     98         return settings.char.generic.Get('fittings', {})
     99 
    100     def DeleteLegacyClientFittings(self):
    101         settings.char.generic.Set('fittings', None)
    102 
    103     def GetFighterTypesInTubes(self):
    104         dogmaLocation = self.GetCurrentDogmaLocation()
    105         return {typeID for typeID in GetFighterNumByTypeIDsInTubes(dogmaLocation).iterkeys()}
    106 
    107     def GetFittingDictForCurrentFittingWindowShip(self, putModuleAmmoInCargo = True):
    108         if self.IsShipSimulated():
    109             return self.GetFittingDictForSimulatedShip(putModuleAmmoInCargo=putModuleAmmoInCargo)
    110         else:
    111             return self.GetFittingDictForActiveShip(putModuleAmmoInCargo=putModuleAmmoInCargo)
    112 
    113     def GetFittingDictForActiveShip(self, putModuleAmmoInCargo = True):
    114         shipID = eveCfg.GetActiveShip()
    115         shipInv = self.invCache.GetInventoryFromId(shipID, locationID=session.stationid)
    116         visibleFittedItems = {i for i in shipInv.List() if inventorycommon.ItemIsVisible(i)}
    117         if InShipInSpace():
    118             try:
    119                 drones = sm.GetService('clientDogmaIM').GetDogmaLocation().GetShip().GetDrones()
    120                 godma = sm.GetService('godma')
    121                 droneInvItems = filter(None, {godma.GetItem(x.itemID) for x in drones.itervalues() if x.locationID != shipID})
    122                 droneDbRows = {blue.DBRow(x.dbrow) for x in droneInvItems}
    123                 for eachDrone in droneDbRows:
    124                     eachDrone.flagID = const.flagDroneBay
    125 
    126                 visibleFittedItems.update(droneDbRows)
    127             except Exception as e:
    128                 log.LogException('issue when getting drones for a fit')
    129 
    130         fitData = self.CreateFittingData(visibleFittedItems, putModuleAmmoInCargo=putModuleAmmoInCargo)
    131         return (shipInv.GetItem().typeID, fitData, visibleFittedItems)
    132 
    133     def IsTypeFittedInActiveShip(self, typeID):
    134         _shipType, fitData, _ = self.GetFittingDictForActiveShip()
    135         for fitTypeID, _fitFlag, _fitQuantity in fitData:
    136             if fitTypeID == typeID:
    137                 return True
    138 
    139         return False
    140 
    141     def FittingDictForRemoteShip(self, shipID, putModuleAmmoInCargo, dockableLocationID, solarSystemID):
    142         inventoryMgr = sm.GetService('invCache').GetInventoryMgr()
    143         visibleFittedItems = inventoryMgr.GetContainerContents(shipID, dockableLocationID, solarSystemID)
    144         return self.GetFittingDictFromItems(visibleFittedItems, putModuleAmmoInCargo)
    145 
    146     def GetFittingDictFromItems(self, visibleFittedItems, putModuleAmmoInCargo):
    147         fittingData = self.CreateFittingData(visibleFittedItems, putModuleAmmoInCargo=putModuleAmmoInCargo)
    148         oringalItemIDByFlagAndTypeID = {(x.typeID, x.flagID):x.itemID for x in visibleFittedItems}
    149         for eachItem in visibleFittedItems:
    150             if IsFighterTubeFlag(eachItem.flagID):
    151                 fittingData.append((eachItem.typeID, eachItem.flagID, eachItem.stacksize))
    152 
    153         return (fittingData, oringalItemIDByFlagAndTypeID)
    154 
    155     def GetFittingDictForSimulatedShip(self, putModuleAmmoInCargo = True):
    156         dogmaLocation = sm.GetService('clientDogmaIM').GetFittingDogmaLocation()
    157         shipID = dogmaLocation.GetCurrentShipID()
    158         shipItem = dogmaLocation.GetDogmaItem(shipID)
    159         fittedItems = shipItem.GetFittedItems()
    160         visibleFittedItems = (i for i in fittedItems.values() if inventorycommon.ItemIsVisible(i))
    161         cargoItems = dogmaLocation.GetHoldItems(const.flagCargo)
    162         fightersInTubes = GetFightersInTubes(dogmaLocation)
    163         fighterBayItems = dogmaLocation.GetHoldItems(const.flagFighterBay)
    164         items = list(visibleFittedItems)
    165         items += shipItem.GetDrones().values()
    166         items += cargoItems.values()
    167         items += fightersInTubes
    168         items += fighterBayItems.values()
    169         fitData = self.CreateFittingData(items, putModuleAmmoInCargo=putModuleAmmoInCargo)
    170         return (shipItem.typeID, fitData, items)
    171 
    172     def CreateFittingData(self, items, putModuleAmmoInCargo = True):
    173         fitData = []
    174         dronesByType = defaultdict(int)
    175         chargesByType = defaultdict(int)
    176         iceByType = defaultdict(int)
    177         fightersByType = defaultdict(int)
    178         implantsByType = defaultdict(int)
    179         modulesInCargo = defaultdict(int)
    180         filamentsByType = defaultdict(int)
    181         otherByType = defaultdict(int)
    182         for item in items:
    183             typeID = item.typeID
    184             flagID = item.flagID
    185             if IsShipFittingFlag(flagID) and IsShipFittable(item.categoryID):
    186                 fitData.append((int(typeID), int(flagID), 1))
    187             elif item.categoryID == const.categoryDrone and flagID == const.flagDroneBay:
    188                 dronesByType[typeID] += getattr(item, 'stacksize', 1)
    189             elif item.categoryID == const.categoryFighter and flagID == const.flagFighterBay:
    190                 fightersByType[typeID] += getattr(item, 'stacksize', 1)
    191             elif item.categoryID == const.categoryFighter and IsFighterTubeFlag(flagID):
    192                 fightersByType[typeID] += getattr(item, 'stacksize', 1)
    193             elif item.categoryID == const.categoryCharge and flagID == const.flagCargo:
    194                 chargesByType[typeID] += getattr(item, 'stacksize', 1)
    195             elif item.categoryID == const.categoryImplant and flagID == const.flagCargo:
    196                 implantsByType[typeID] += getattr(item, 'stacksize', 1)
    197             elif item.categoryID in (const.categoryModule, const.categorySubSystem) and flagID == const.flagCargo:
    198                 modulesInCargo[typeID] += getattr(item, 'stacksize', 1)
    199             elif hasattr(item, 'groupID') and item.groupID == const.groupIceProduct and flagID == const.flagCargo:
    200                 iceByType[typeID] += getattr(item, 'stacksize', 1)
    201             elif putModuleAmmoInCargo and item.categoryID == const.categoryCharge and flagID in const.moduleSlotFlags:
    202                 chargesByType[typeID] += getattr(item, 'stacksize', 1)
    203             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_FILAMENTS):
    204                 filamentsByType[typeID] += getattr(item, 'stacksize', 1)
    205             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_OTHER_CARGO_HOLD_TYPES):
    206                 otherByType[typeID] += getattr(item, 'stacksize', 1)
    207 
    208         fitData += self.GetDataToAddToFitData(const.flagDroneBay, dronesByType)
    209         fitData += self.GetDataToAddToFitData(const.flagFighterBay, fightersByType)
    210         fitData += self.GetDataToAddToFitData(const.flagCargo, chargesByType)
    211         fitData += self.GetDataToAddToFitData(const.flagCargo, iceByType)
    212         fitData += self.GetDataToAddToFitData(const.flagCargo, implantsByType)
    213         fitData += self.GetDataToAddToFitData(const.flagCargo, modulesInCargo)
    214         fitData += self.GetDataToAddToFitData(const.flagCargo, filamentsByType)
    215         fitData += self.GetDataToAddToFitData(const.flagCargo, otherByType)
    216         return fitData
    217 
    218     def GetLoadedChargeTypes(self, items):
    219         chargesToLoad = []
    220         for item in items:
    221             typeID = item.typeID
    222             flagID = item.flagID
    223             if item.categoryID == const.categoryCharge and flagID != const.flagCargo:
    224                 chargesToLoad.append((typeID, flagID))
    225 
    226         return chargesToLoad
    227 
    228     def GetDataToAddToFitData(self, flag, qtyByTypeID):
    229         data = []
    230         for typeID, quantity in qtyByTypeID.iteritems():
    231             data.append((int(typeID), int(flag), int(quantity)))
    232 
    233         return data
    234 
    235     def DisplayFittingFromItems(self, shipTypeID, items):
    236         fitting = self.GetFittingFromItems(shipTypeID, items)
    237         self.DisplayFitting(fitting)
    238 
    239     def GetFittingFromItems(self, shipTypeID, items):
    240         newItems = []
    241         for item in items:
    242             if not hasattr(item, 'flagID'):
    243                 item.flagID = item.flag
    244             if not hasattr(item, 'stacksize'):
    245                 item.stacksize = item.qtyDropped + item.qtyDestroyed
    246             item.categoryID = evetypes.GetCategoryID(item.typeID)
    247             newItems.append(item)
    248 
    249         fitData = self.CreateFittingData(newItems)
    250         fitting = utillib.KeyVal()
    251         fitting.shipTypeID = shipTypeID
    252         fitting.name = evetypes.GetName(shipTypeID)
    253         fitting.ownerID = None
    254         fitting.fittingID = None
    255         fitting.description = ''
    256         fitting.fitData = fitData
    257         return fitting
    258 
    259     def PersistFitting(self, ownerID, name, description, fit = None):
    260         if name is None or name.strip() == '':
    261             raise UserError('FittingNeedsToHaveAName')
    262         name = name.strip()
    263         fitting = utillib.KeyVal()
    264         fitting.name = name[:const.maxLengthFittingName]
    265         fitting.description = description[:const.maxLengthFittingDescription]
    266         self.PrimeFittings(ownerID)
    267         if ownerID == session.corpid:
    268             maxFittings = const.maxCorpFittings
    269         else:
    270             maxFittings = const.maxCharFittings
    271         if len(self.fittings[ownerID]) >= maxFittings:
    272             owner = cfg.eveowners.Get(ownerID).ownerName
    273             raise UserError('OwnerMaxFittings', {'owner': owner,
    274              'maxFittings': maxFittings})
    275         if fit is None:
    276             fitting.shipTypeID, fitting.fitData, _ = self.GetFittingDictForActiveShip()
    277         else:
    278             fitting.shipTypeID, fitting.fitData = fit
    279         self.VerifyFitting(fitting)
    280         fitting.ownerID = ownerID
    281         existingFitting = self._GetFittingWithName(fitting.shipTypeID, ownerID, name)
    282         if existingFitting:
    283             if ownerID == session.corpid:
    284                 question = 'UpdateFitCorp'
    285             else:
    286                 question = 'UpdateFitPersonal'
    287             ret = prompt_player_fsd_dialog(question, {'fittingName': name}, uiconst.YESNO, suppress=uiconst.ID_YES)
    288             if ret != uiconst.ID_YES:
    289                 return
    290             if not fitting.description:
    291                 fitting.description = existingFitting.description
    292         fittingMgr = self.GetFittingMgr(ownerID)
    293         if existingFitting:
    294             fittingID = fittingMgr.UpdateFitting(ownerID, fitting, fittingID=existingFitting.fittingID)
    295         else:
    296             fittingID = fittingMgr.SaveFitting(ownerID, fitting)
    297         if fittingID > 0:
    298             if existingFitting:
    299                 fitting.fittingID = fittingID
    300                 fitting.savedDate = blue.os.GetWallclockTime()
    301                 if ownerID in self.fittings:
    302                     self.fittings[ownerID][fittingID] = fitting
    303             self.UpdateFittingWindow()
    304             sm.ScatterEvent('OnFittingsUpdated', fitting)
    305         return fittingID
    306 
    307     def _GetFittingWithName(self, shipTypeID, ownerID, name):
    308         fittingsForOwner = self.GetFittings(ownerID)
    309         for eachFitID, eachFit in fittingsForOwner.iteritems():
    310             if eachFit.shipTypeID == shipTypeID and eachFit.name == name:
    311                 return eachFit
    312 
    313     def PersistManyFittings(self, ownerID, fittings):
    314         if ownerID == session.corpid:
    315             maxFittings = const.maxCorpFittings
    316         else:
    317             maxFittings = const.maxCharFittings
    318         self.PrimeFittings(ownerID)
    319         if len(self.fittings[ownerID]) + len(fittings) > maxFittings:
    320             owner = cfg.eveowners.Get(ownerID).ownerName
    321             raise UserError('OwnerMaxFittings', {'owner': owner,
    322              'maxFittings': maxFittings})
    323         fittingsToSave = {}
    324         tmpFittingID = INVALID_FITTING_ID
    325         for fitt in fittings:
    326             if fitt.name is None or fitt.name.strip() == '':
    327                 raise UserError('FittingNeedsToHaveAName')
    328             fitting = utillib.KeyVal()
    329             fitting.fittingID = tmpFittingID
    330             fitting.name = fitt.name.strip()[:const.maxLengthFittingName]
    331             fitting.description = fitt.description[:const.maxLengthFittingDescription]
    332             fitting.shipTypeID = fitt.shipTypeID
    333             fitting.fitData = fitt.fitData
    334             self.VerifyFitting(fitting)
    335             fitting.ownerID = ownerID
    336             fittingsToSave[tmpFittingID] = fitting
    337             tmpFittingID -= 1
    338 
    339         newFittingIDs = self.GetFittingMgr(ownerID).SaveManyFittings(ownerID, fittingsToSave)
    340         for row in newFittingIDs:
    341             self.fittings[ownerID][row.realFittingID] = fittingsToSave[row.tempFittingID]
    342             self.fittings[ownerID][row.realFittingID].fittingID = row.realFittingID
    343 
    344         self.UpdateFittingWindow()
    345         return fitting
    346 
    347     def VerifyFitting(self, fitting):
    348         if fitting.name.find('@@') != INVALID_FITTING_ID or fitting.description.find('@@') != INVALID_FITTING_ID:
    349             raise UserError('InvalidFittingInvalidCharacter')
    350         if fitting.shipTypeID is None:
    351             raise UserError('InvalidFittingDataTypeID', {'typeName': fitting.shipTypeID})
    352         shipTypeName = evetypes.GetNameOrNone(fitting.shipTypeID)
    353         if shipTypeName is None:
    354             raise UserError('InvalidFittingDataTypeID', {'typeName': fitting.shipTypeID})
    355         if evetypes.GetCategoryID(fitting.shipTypeID) not in (const.categoryShip, const.categoryStructure):
    356             raise UserError('InvalidFittingDataShipNotShip', {'typeName': shipTypeName})
    357         if len(fitting.fitData) == 0:
    358             raise UserError('ParseFittingFittingDataEmpty')
    359         for typeID, flag, qty in fitting.fitData:
    360             if not evetypes.Exists(typeID):
    361                 raise UserError('InvalidFittingDataTypeID', {'typeID': typeID})
    362             try:
    363                 int(flag)
    364             except TypeError:
    365                 raise UserError('InvalidFittingDataInvalidFlag', {'type': typeID})
    366 
    367             if not (IsShipFittingFlag(flag) or flag in (const.flagDroneBay, const.flagCargo, const.flagFighterBay)):
    368                 raise UserError('InvalidFittingDataInvalidFlag', {'type': typeID})
    369             try:
    370                 int(qty)
    371             except TypeError:
    372                 raise UserError('InvalidFittingDataInvalidQuantity', {'type': typeID})
    373 
    374             if qty == 0:
    375                 raise UserError('InvalidFittingDataInvalidQuantity', {'type': typeID})
    376 
    377         return True
    378 
    379     def GetAllFittingsForCharacter(self):
    380         ret = uthread.parallel([(self.GetFittings, (session.charid,)),
    381          (self.GetFittings, (session.corpid,)),
    382          (self.GetCommunityFittings, ()),
    383          (self.GetAllianceFittings, ())])
    384         return ret
    385 
    386     def GetCommunityFittings(self):
    387         if not AreCommunityFittingsEnabled(sm.GetService('machoNet')):
    388             return {}
    389         try:
    390             if COMMUNITY_FITTING_CORP not in self.fittings:
    391                 corpFittingMgr = sm.RemoteSvc('corpFittingMgr')
    392                 communityFittings = corpFittingMgr.GetCommunityFittings()
    393                 self.fittings[COMMUNITY_FITTING_CORP] = communityFittings
    394             return self.fittings[COMMUNITY_FITTING_CORP]
    395         except Exception as e:
    396             self.LogException(e)
    397             return {}
    398 
    399     def GetAllianceFittings(self):
    400         if session.allianceid:
    401             return self.GetFittings(session.allianceid)
    402         return {}
    403 
    404     def GetFittings(self, ownerID):
    405         self.PrimeFittings(ownerID)
    406         return self.fittings[ownerID]
    407 
    408     def GetFittingsForType(self, ownerID, typeID):
    409         allFittings = self.GetFittings(ownerID)
    410         return [ fitting for fitting in allFittings.items() if fitting[1]['shipTypeID'] == typeID ]
    411 
    412     def PrimeFittings(self, ownerID):
    413         if ownerID not in self.fittings:
    414             self.fittings[ownerID] = self.GetFittingMgr(ownerID).GetFittings(ownerID)
    415 
    416     def DeleteFitting(self, ownerID, fittingID):
    417         self.LogInfo('deleting fitting', fittingID, 'from owner', ownerID)
    418         self.GetFittingMgr(ownerID).DeleteFitting(ownerID, fittingID)
    419         if ownerID in self.fittings and fittingID in self.fittings[ownerID]:
    420             del self.fittings[ownerID][fittingID]
    421         self.UpdateFittingWindow()
    422 
    423     def DeleteManyFittings(self, fittingsByOwnerID):
    424         calls = []
    425         ownerIDs = fittingsByOwnerID.keys()
    426         totalFittings = 0
    427         for eachOwnerID in ownerIDs:
    428             fittingIDs = fittingsByOwnerID.get(eachOwnerID, [])
    429             totalFittings += len(fittingIDs)
    430             calls.append((self.GetFittingMgr(eachOwnerID).DeleteManyFittings, (eachOwnerID, fittingIDs)))
    431 
    432         ret = uthread.parallel(calls)
    433         fittingsDeleted = 0
    434         for x in ret:
    435             fittingsDeleted += len(x)
    436 
    437         if len(ownerIDs) > 1:
    438             ownerText = self.GetDeletedFittingsText(ownerIDs, fittingsByOwnerID, ret)
    439         else:
    440             ownerText = ''
    441         prompt_player_fsd_dialog('DeletedManyFittings', {'numFittings': fittingsDeleted,
    442          'ownerText': ownerText})
    443 
    444     def GetDeletedFittingsText(self, ownerIDs, fittingsByOwnerID, deletedFittings):
    445         textList = []
    446         for i, ownerID in enumerate(ownerIDs):
    447             if not ownerID:
    448                 continue
    449             fittingIDs = fittingsByOwnerID.get(ownerID, [])
    450             if fittingIDs:
    451                 numDeleted = len(deletedFittings[i])
    452                 numAttempted = len(fittingIDs)
    453                 msgName = 'UI/Fitting/FittingWindow/OwnerAndDeletedFittingNum'
    454                 if numDeleted != numAttempted:
    455                     msgName = 'UI/Fitting/FittingWindow/OwnerAndDeletedFittingNumWithAttempted'
    456                 ownerName = cfg.eveowners.Get(ownerID).name
    457                 text = localization.GetByLabel(msgName, numFittings=numDeleted, ownerName=ownerName, numAttempted=len(fittingIDs))
    458                 text = u'  \u2022 ' + text
    459                 textList.append(text)
    460 
    461         return '<br>'.join(textList)
    462 
    463     def LoadFittingFromFittingIDAndGetBuyOptionOnFailure(self, ownerID, fittingID):
    464         fitting = self.fittings.get(ownerID, {}).get(fittingID, None)
    465         return self.LoadFittingFromFittingListAndGetBuyOptionOnFailure(fitting)
    466 
    467     def LoadFittingFromFittingListAndGetBuyOptionOnFailure(self, fitting):
    468         failedToLoad = self.LoadFitting(fitting, getFailedDict=True)
    469         failedToLoadCounter = Counter({x[0]:x[1] for x in failedToLoad})
    470         if failedToLoadCounter:
    471             OpenBuyAllBox(failedToLoadCounter, fitting)
    472         else:
    473             sm.GetService('ghostFittingSvc').TryExitSimulation()
    474 
    475     def LoadFittingFromFittingID(self, ownerID, fittingID, getFailedDict = False):
    476         fitting = self.fittings.get(ownerID, {}).get(fittingID, None)
    477         return self.LoadFitting(fitting, getFailedDict=getFailedDict)
    478 
    479     def CheckBusyFittingAndRaiseIfNeeded(self):
    480         if self.busyFitting:
    481             sm.GetService('loading').ProgressWnd('', '', 1, 1)
    482             raise UserError('CustomInfo', {'info': localization.GetByLabel('UI/Fitting/FittingWindow/FittingManagement/BusyFittingWarning')})
    483 
    484     def DoFitManyShips(self, shipTypeID, fitInfo, cargoItemsByType, fitRigs, fittingName, maxAvailableFitting, itemContainerID = None):
    485         shipAccess = sm.StartService('gameui').GetShipAccess()
    486         try:
    487             failedInfo = None
    488             self.busyFitting = True
    489             sm.GetService('inv').ChangeTreeUpdatingState(isUpdateEnabled=False)
    490             failedInfo = shipAccess.FitShips(shipTypeID=shipTypeID, fitInfo=fitInfo, itemLocationID=session.stationid or session.structureid, cargoItemsByType=cargoItemsByType, fitRigs=fitRigs, name=fittingName, numToFit=maxAvailableFitting, itemContainerID=itemContainerID)
    491         finally:
    492             self.busyFitting = False
    493             sm.GetService('inv').ChangeTreeUpdatingState(isUpdateEnabled=True)
    494             if failedInfo:
    495                 failedToLoad, failedShipID = failedInfo
    496                 if failedShipID:
    497                     self._RenameShip(failedShipID)
    498                 if failedToLoad:
    499                     self.ShowFailedToLoadMsg(failedToLoad)
    500 
    501     def _RenameShip(self, failedShipID):
    502         badFitName = localization.GetByLabel('UI/Fitting/FittingWindow/FittingManagement/BadFitFittingFailed')
    503         self.invCache.GetInventoryMgr().SetLabel(failedShipID, badFitName)
    504 
    505     def GetMaxAvailabeAndMissingForFullFit(self, fitRigs, itemTypes, numToFit, qtyByTypeID, rigTypeIDs):
    506         missingForFullFit = {}
    507         maxRoundsForEachTypeID = {}
    508         for itemTypID, qtyNeeded in itemTypes.iteritems():
    509             if not fitRigs and itemTypID in rigTypeIDs:
    510                 continue
    511             qtyInHangar = qtyByTypeID.get(itemTypID, 0)
    512             rounds = int(qtyInHangar / qtyNeeded)
    513             maxRoundsForEachTypeID[itemTypID] = rounds
    514             if rounds < numToFit:
    515                 missingForFullFit[itemTypID] = qtyNeeded * numToFit - qtyInHangar
    516 
    517         maxAvailableFitting = min(maxRoundsForEachTypeID.values()) if maxRoundsForEachTypeID else None
    518         return (maxAvailableFitting, missingForFullFit)
    519 
    520     def GetMaxAvailable(self, fitRigs, itemTypes, qtyByTypeID, rigTypeIDs):
    521         maxRoundsForEachTypeID = {}
    522         for itemTypID, qtyNeeded in itemTypes.iteritems():
    523             if not fitRigs and itemTypID in rigTypeIDs:
    524                 continue
    525             qtyInHangar = qtyByTypeID.get(itemTypID, 0)
    526             rounds = int(qtyInHangar / qtyNeeded)
    527             maxRoundsForEachTypeID[itemTypID] = rounds
    528 
    529     def LoadFitting(self, fitting, getFailedDict = False):
    530         self.CheckBusyFittingAndRaiseIfNeeded()
    531         self._CheckValidFittingLocation(fitting)
    532         shipInv = self.invCache.GetInventoryFromId(eveCfg.GetActiveShip())
    533         shipTypeID = shipInv.GetItem().typeID
    534         if shipTypeID != fitting.shipTypeID:
    535             raise UserError('ShipTypeInFittingNotSameAsShip')
    536         fittingObj = Fitting(fitting.fitData, shipInv)
    537         rigsByFlags = fittingObj.GetRigsByFlag()
    538         fitRigs = False
    539         cargoItemsByType = defaultdict(int)
    540         if bool(rigsByFlags):
    541             fittedRigsByFlagID = self.GetFittedRigsByFlagID()
    542             rigMismatch = IsThereRigMismatch(fittedRigsByFlagID, rigsByFlags)
    543             sameRigsInSameSlots = rigsByFlags == fittedRigsByFlagID
    544             if rigMismatch and not IsModularShip(shipTypeID):
    545                 prompt_player_fsd_dialog('CustomNotify', {'notify': localization.GetByLabel('UI/Fitting/ShipHasRigsAlready')})
    546             elif IsModularShip(shipTypeID) or sameRigsInSameSlots:
    547                 fitRigs = True
    548             elif prompt_player_fsd_dialog('FitRigs', {}, uiconst.YESNO) == uiconst.ID_YES:
    549                 fitRigs = True
    550             else:
    551                 for flagID, typeID in fittingObj.GetModulesByFlag().iteritems():
    552                     if flagID in const.rigSlotFlags:
    553                         cargoItemsByType[typeID] += 1
    554 
    555         cargoItemsByType = dict(cargoItemsByType)
    556         itemTypes = fittingObj.GetQuantityByType()
    557         itemsToFit = defaultdict(set)
    558         for item in self.invCache.GetInventory(const.containerHangar).List(const.flagHangar):
    559             if item.typeID in itemTypes:
    560                 qtyNeeded = itemTypes[item.typeID]
    561                 if qtyNeeded == 0:
    562                     continue
    563                 quantityToTake = min(item.stacksize, qtyNeeded)
    564                 itemsToFit[item.typeID].add(item.itemID)
    565                 itemTypes[item.typeID] -= quantityToTake
    566 
    567         numSubsystems = fittingObj.GetFittingSubSystemNumber()
    568         if numSubsystems:
    569             alreadyFitted = {x.typeID for x in shipInv.List() if IsSubsystemFlagVisible(x.flagID)}
    570             if numSubsystems != invConst.numVisibleSubsystems:
    571                 raise UserError('CantUnfitSubSystems')
    572             clientDogmaStaticSvc = sm.GetService('clientDogmaStaticSvc')
    573             for flag, module in fittingObj.GetModulesByFlag().iteritems():
    574                 if not IsSubsystemFlagVisible(flag):
    575                     continue
    576                 if module in alreadyFitted:
    577                     continue
    578                 moduleFitsShipType = int(clientDogmaStaticSvc.GetTypeAttribute(module, const.attributeFitsToShipType))
    579                 if shipTypeID != moduleFitsShipType:
    580                     raise UserError('CannotFitSubSystemNotShip', {'subSystemName': (const.UE_TYPEID, module),
    581                      'validShipName': (const.UE_TYPEID, moduleFitsShipType),
    582                      'shipName': (const.UE_TYPEID, shipTypeID)})
    583                 if module in itemTypes and itemTypes[module] > 0:
    584                     raise UserError('CantUnfitSubSystems')
    585 
    586         self.CheckBusyFittingAndRaiseIfNeeded()
    587         self.busyFitting = True
    588         try:
    589             fittingObjKeyVal = fittingObj.GetKeyValForApplyingFit()
    590             failedToLoad = shipInv.FitFitting(eveCfg.GetActiveShip(), shipTypeID, itemsToFit, session.stationid or session.structureid, fittingObjKeyVal, cargoItemsByType, fitRigs)
    591         finally:
    592             self.busyFitting = False
    593 
    594         if settings.user.ui.Get('useFittingNameForShips', 0):
    595             fittingName = StripTags(fitting.get('name'))
    596             fittingName = fittingName[:20]
    597             self.invCache.GetInventoryMgr().SetLabel(eveCfg.GetActiveShip(), fittingName)
    598         if getFailedDict:
    599             return failedToLoad
    600         self.ShowFailedToLoadMsg(failedToLoad)
    601 
    602     def ShowFailedToLoadMsg(self, failedToLoad):
    603         textList = []
    604         for typeID, qty in failedToLoad:
    605             if qty > 0:
    606                 typeName = evetypes.GetName(typeID)
    607                 link = GetShowInfoLink(typeID, typeName)
    608                 textList.append((typeName.lower(), '%sx %s' % (qty, link)))
    609 
    610         if textList:
    611             textList = SortListOfTuples(textList)
    612             text = '<br>'.join(textList)
    613             text = localization.GetByLabel('UI/Fitting/MissingItems', types=text)
    614             prompt_player_fsd_dialog('CustomInfo', {'info': text}, modal=False)
    615 
    616     def _CheckValidFittingLocation(self, fitting):
    617         if session.stationid is None and session.structureid is None:
    618             raise UserError('CannotLoadFittingInSpace')
    619         if fitting is None:
    620             raise UserError('FittingDoesNotExist')
    621 
    622     def GetQtyInLocationByTypeIDs(self, itemTypes, onlyGetNonSingletons = False, locationID = None):
    623         if locationID is None:
    624             containerLocation = self.invCache.GetInventory(const.containerHangar)
    625             itemList = containerLocation.List(const.flagHangar)
    626         else:
    627             containerLocation = self.invCache.GetInventoryFromId(locationID)
    628             itemList = containerLocation.List()
    629         qtyByTypeID = defaultdict(int)
    630         for item in itemList:
    631             if item.typeID in itemTypes:
    632                 if onlyGetNonSingletons and item.quantity < 0:
    633                     continue
    634                 qtyByTypeID[item.typeID] += item.stacksize
    635 
    636         return qtyByTypeID
    637 
    638     def FindPersonalContainers(self):
    639         hangar = self.invCache.GetInventory(const.containerHangar)
    640         personalContainers = set()
    641         validContainers = (invConst.groupAuditLogSecureContainer,
    642          invConst.groupSecureCargoContainer,
    643          invConst.groupCargoContainer,
    644          invConst.groupFreightContainer)
    645         for item in hangar.List(const.flagHangar):
    646             if item.groupID in validContainers and item.ownerID == session.charid and item.singleton:
    647                 personalContainers.add(item)
    648 
    649         return personalContainers
    650 
    651     def GetTypesToFit(self, fitting, shipInv):
    652         fittingObj = Fitting(fitting.fitData, shipInv)
    653         return (fittingObj.GetChargesByType(),
    654          fittingObj.GetDronesByType(),
    655          fittingObj.GetFigthersByType(),
    656          fittingObj.GetIceByType(),
    657          fittingObj.GetQuantityByType(),
    658          fittingObj.GetModulesByFlag(),
    659          fittingObj.GetRigsByFlag(),
    660          fittingObj.GetFittingSubSystemNumber())
    661 
    662     def GetFittedRigsByFlagID(self):
    663         rigsFittedByFlagID = {}
    664         shipInv = self.invCache.GetInventoryFromId(eveCfg.GetActiveShip(), locationID=session.stationid)
    665         for item in shipInv.List():
    666             if const.flagRigSlot0 <= item.flagID <= const.flagRigSlot7:
    667                 rigsFittedByFlagID[item.flagID] = item.typeID
    668 
    669         return rigsFittedByFlagID
    670 
    671     def UpdateFittingWindow(self):
    672         wnd = FittingMgmt.GetIfOpen()
    673         if wnd is not None:
    674             wnd.DrawFittings()
    675 
    676     def ChangeNameAndDescription(self, fittingID, ownerID, name, description):
    677         if name is None or name.strip() == '':
    678             raise UserError('FittingNeedsToHaveAName')
    679         name = name.strip()
    680         fittings = self.GetFittings(ownerID)
    681         if fittingID in fittings:
    682             fitting = fittings[fittingID]
    683             if name != fitting.name or description != fitting.description:
    684                 if name.find('@@') != INVALID_FITTING_ID or description.find('@@') != INVALID_FITTING_ID:
    685                     raise UserError('InvalidFittingInvalidCharacter')
    686                 self.GetFittingMgr(ownerID).UpdateNameAndDescription(fittingID, ownerID, name, description)
    687                 self.fittings[ownerID][fittingID].name = name
    688                 self.fittings[ownerID][fittingID].description = description
    689         self.UpdateFittingWindow()
    690 
    691     def GetFitting(self, ownerID, fittingID):
    692         self.PrimeFittings(ownerID)
    693         if fittingID in self.fittings[ownerID]:
    694             return self.fittings[ownerID][fittingID]
    695 
    696     def CheckFittingExist(self, ownerID, shipTypeID, fitData):
    697         fittings = self.GetFittings(ownerID)
    698         fittingExists = False
    699         for fitting in fittings.itervalues():
    700             if fitting.shipTypeID != shipTypeID:
    701                 continue
    702             if fitting.fitData != fitData:
    703                 continue
    704             fittingExists = True
    705 
    706         return fittingExists
    707 
    708     def DisplayFittingFromString(self, fittingString, name = None):
    709         fitting, truncated = self.GetFittingFromString(fittingString, name)
    710         if fitting == INVALID_FITTING_ID:
    711             raise UserError('FittingInvalidForViewing')
    712         self.DisplayFitting(fitting, truncated=truncated)
    713 
    714     def DisplayFitting(self, fitting, truncated = False):
    715         if uicore.uilib.Key(uiconst.VK_SHIFT):
    716             fittingsList = fitting.fitData[:]
    717             fittingsList.sort()
    718             newFittingStr = '%s' % fittingsList
    719             windowID = 'ViewFitting_%s' % newFittingStr
    720         else:
    721             windowID = 'ViewFitting'
    722         wnd = ViewFitting.GetIfOpen(windowID=windowID)
    723         if wnd:
    724             wnd.ReloadWnd(windowID=windowID, fitting=fitting, truncated=truncated)
    725             wnd.Maximize()
    726         else:
    727             ViewFitting.Open(windowID=windowID, fitting=fitting, truncated=truncated)
    728 
    729     def GetStringForFitting(self, fitting):
    730         typesDict = defaultdict(int)
    731         drones = {}
    732         fighters = {}
    733         charges = {}
    734         ice = {}
    735         implants = {}
    736         modules = {}
    737         filaments = {}
    738         otherCargoItems = {}
    739         for typeID, flag, qty in fitting.fitData:
    740             categoryID = evetypes.GetCategoryID(typeID)
    741             groupID = evetypes.GetGroupID(typeID)
    742             if IsShipFittable(categoryID):
    743                 if flag == const.flagCargo:
    744                     modules[typeID] = qty
    745                 else:
    746                     typesDict[typeID] += 1
    747             elif categoryID == const.categoryDrone:
    748                 drones[typeID] = qty
    749             elif categoryID == const.categoryFighter:
    750                 fighters[typeID] = qty
    751             elif categoryID == const.categoryCharge:
    752                 charges[typeID] = qty
    753             elif categoryID == const.categoryImplant:
    754                 implants[typeID] = qty
    755             elif groupID == const.groupIceProduct:
    756                 ice[typeID] = qty
    757             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_FILAMENTS):
    758                 filaments[typeID] = qty
    759             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_OTHER_CARGO_HOLD_TYPES):
    760                 otherCargoItems[typeID] = qty
    761 
    762         retList = []
    763         subString = str(fitting.shipTypeID)
    764         retList.append(subString)
    765         for eachDict in [typesDict,
    766          drones,
    767          fighters,
    768          charges,
    769          ice,
    770          implants,
    771          filaments,
    772          otherCargoItems]:
    773             for typeID, qty in eachDict.iteritems():
    774                 subString = '%s;%s' % (typeID, qty)
    775                 retList.append(subString)
    776 
    777         for typeID, qty in modules.iteritems():
    778             subString = '%s%s;%s' % (typeID, CARGO_MARKER, qty)
    779             retList.append(subString)
    780 
    781         ret = ':'.join(retList)
    782         ret += '::'
    783         return ret
    784 
    785     def GetFittingFromString(self, fittingString, name = None):
    786         effectSlots = {const.effectHiPower: const.flagHiSlot0,
    787          const.effectMedPower: const.flagMedSlot0,
    788          const.effectLoPower: const.flagLoSlot0,
    789          const.effectRigSlot: const.flagRigSlot0,
    790          const.effectSubSystem: const.flagSubSystemSlot0,
    791          const.effectServiceSlot: const.flagServiceSlot0}
    792         truncated = False
    793         if not fittingString.endswith('::'):
    794             truncated = True
    795             fittingString = fittingString[:fittingString.rfind(':')]
    796         data = fittingString.split(':')
    797         fitting = utillib.KeyVal()
    798         fitData = []
    799         for line in data:
    800             typeInfo = line.split(';')
    801             if line == '':
    802                 continue
    803             if len(typeInfo) == 1:
    804                 try:
    805                     fitting.shipTypeID = int(typeInfo[0])
    806                 except ValueError:
    807                     return (INVALID_FITTING_ID, truncated)
    808 
    809                 continue
    810             typeID, qty = typeInfo
    811             inCargo = False
    812             if typeID.endswith(CARGO_MARKER):
    813                 typeID = typeID[:-1]
    814                 inCargo = True
    815             try:
    816                 typeID, qty = int(typeID), int(qty)
    817             except ValueError:
    818                 continue
    819 
    820             powerEffectID = sm.GetService('godma').GetPowerEffectForType(typeID)
    821             if powerEffectID is not None and not inCargo:
    822                 startSlot = effectSlots[powerEffectID]
    823                 for flag in xrange(startSlot, startSlot + qty):
    824                     fitData.append((typeID, flag, 1))
    825 
    826                 effectSlots[powerEffectID] = flag + 1
    827             else:
    828                 categoryID = evetypes.GetCategoryID(typeID)
    829                 groupID = evetypes.GetGroupID(typeID)
    830                 if categoryID == const.categoryDrone:
    831                     fitData.append((typeID, const.flagDroneBay, qty))
    832                 if categoryID == const.categoryFighter:
    833                     fitData.append((typeID, const.flagFighterBay, qty))
    834                 elif categoryID in (const.categoryCharge,
    835                  const.categoryImplant,
    836                  const.categoryModule,
    837                  const.categorySubSystem):
    838                     fitData.append((typeID, const.flagCargo, qty))
    839                 elif groupID == const.groupIceProduct:
    840                     fitData.append((typeID, const.flagCargo, qty))
    841                 elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_FILAMENTS):
    842                     fitData.append((typeID, const.flagCargo, qty))
    843                 elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_OTHER_CARGO_HOLD_TYPES):
    844                     fitData.append((typeID, const.flagCargo, qty))
    845                 else:
    846                     continue
    847 
    848         shipName = name or evetypes.GetName(fitting.shipTypeID)
    849         fitting.name = shipName
    850         fitting.ownerID = None
    851         fitting.fittingID = None
    852         fitting.description = ''
    853         fitting.fitData = fitData
    854         return (fitting, truncated)
    855 
    856     def GetFittingInfoScrollList(self, fitting):
    857         scrollListData = self.GetFittingInfoForScrollList(fitting)
    858         scrollList = []
    859         for d in scrollListData:
    860             if isinstance(d, KeyVal):
    861                 entry = GetFromClass(FittingModuleEntry, d)
    862                 scrollList.append(entry)
    863             else:
    864                 scrollList.append(d)
    865 
    866         return scrollList
    867 
    868     def GetFittingInfoForScrollList(self, fitting):
    869         scrollListData = []
    870         typesByRack = self.GetTypesByRack(fitting)
    871         for key, effectID in [('hiSlots', const.effectHiPower),
    872          ('medSlots', const.effectMedPower),
    873          ('lowSlots', const.effectLoPower),
    874          ('rigSlots', const.effectRigSlot),
    875          ('subSystems', const.effectSubSystem),
    876          ('serviceSlots', const.effectServiceSlot)]:
    877             slots = typesByRack[key]
    878             if len(slots) < 1:
    879                 continue
    880             label = dogma.data.get_effect_display_name(effectID)
    881             scrollListData.append(GetFromClass(Header, {'label': label}))
    882             dataForEffect = []
    883             for typeID, qty in slots.iteritems():
    884                 data = self._GetDataForFittingEntry(typeID, qty)
    885                 data.singleton = 1
    886                 data.effectID = effectID
    887                 dataForEffect.append((evetypes.GetGroupID(typeID), data))
    888 
    889             dataForEffect = SortListOfTuples(dataForEffect)
    890             scrollListData.extend(dataForEffect)
    891 
    892         for qtyByTypeIdDict, headerLabelPath, isValidGroup in ((typesByRack['charges'], 'UI/Generic/Charges', True),
    893          (typesByRack['ice'], 'UI/Inflight/MoonMining/Processes/Fuel', True),
    894          (typesByRack['drones'], 'UI/Drones/Drones', True),
    895          (typesByRack['fighters'], 'UI/Common/Fighters', True),
    896          (typesByRack['implants'], 'UI/Fitting/FittingWindow/ImplantsAndBoosters', True),
    897          (typesByRack['modules'], 'UI/Fitting/FittingWindow/FittingManagement/Modules', True),
    898          (typesByRack['filaments'], 'UI/Fitting/FittingWindow/FittingManagement/Filaments', True),
    899          (typesByRack['otherInCargo'], 'UI/Fitting/FittingWindow/FittingManagement/OtherItemsInCargo', True),
    900          (typesByRack['other'], 'UI/Fitting/FittingWindow/FittingManagement/OtherItems', False)):
    901             if len(qtyByTypeIdDict) > 0:
    902                 scrollListData.append(GetFromClass(Header, {'label': localization.GetByLabel(headerLabelPath)}))
    903                 scrollListData += self._GetFittingEntryDataForGroup(qtyByTypeIdDict, isValidGroup)
    904 
    905         return scrollListData
    906 
    907     def _GetFittingEntryDataForGroup(self, qtyByTypeIdDict, isValidGroup):
    908         dataList = []
    909         for typeID, qty in qtyByTypeIdDict.iteritems():
    910             data = self._GetDataForFittingEntry(typeID, qty, isValidGroup)
    911             dataList.append(data)
    912 
    913         return dataList
    914 
    915     def _GetDataForFittingEntry(self, typeID, qty, isValidGroup = True):
    916         isObsolete = bool(sm.GetService('clientDogmaStaticSvc').GetTypeAttribute(typeID, const.attributeModuleIsObsolete))
    917         data = utillib.KeyVal(typeID=typeID, showinfo=1, getIcon=True, label=unicode(FmtAmt(qty)) + 'x ' + evetypes.GetName(typeID), isValidGroup=isValidGroup, isObsolete=isObsolete)
    918         return data
    919 
    920     def GetTypesByRack(self, fitting):
    921         ret = {'hiSlots': defaultdict(int),
    922          'medSlots': defaultdict(int),
    923          'lowSlots': defaultdict(int),
    924          'rigSlots': defaultdict(int),
    925          'subSystems': defaultdict(int),
    926          'serviceSlots': defaultdict(int),
    927          'charges': {},
    928          'drones': {},
    929          'ice': {},
    930          'fighters': {},
    931          'implants': {},
    932          'modules': {},
    933          'filaments': {},
    934          'otherInCargo': {},
    935          'other': {}}
    936         for typeID, flag, qty in fitting.fitData:
    937             if evetypes.GetCategoryID(typeID) == const.categoryCharge:
    938                 ret['charges'][typeID] = qty
    939             elif evetypes.GetCategoryID(typeID) == const.categoryImplant:
    940                 ret['implants'][typeID] = qty
    941             elif evetypes.GetGroupID(typeID) == const.groupIceProduct:
    942                 ret['ice'][typeID] = qty
    943             elif flag in const.hiSlotFlags:
    944                 ret['hiSlots'][typeID] += 1
    945             elif flag in const.medSlotFlags:
    946                 ret['medSlots'][typeID] += 1
    947             elif flag in const.loSlotFlags:
    948                 ret['lowSlots'][typeID] += 1
    949             elif flag in const.rigSlotFlags:
    950                 ret['rigSlots'][typeID] += 1
    951             elif const.flagServiceSlot0 <= flag <= const.flagServiceSlot7:
    952                 ret['serviceSlots'][typeID] += 1
    953             elif IsSubsystemFlagVisible(flag):
    954                 ret['subSystems'][typeID] += 1
    955             elif flag == const.flagDroneBay:
    956                 ret['drones'][typeID] = qty
    957             elif flag == const.flagFighterBay:
    958                 ret['fighters'][typeID] = qty
    959             elif evetypes.GetCategoryID(typeID) in (const.categoryModule, const.categorySubSystem) and flag == const.flagCargo:
    960                 ret['modules'][typeID] = qty
    961             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_FILAMENTS):
    962                 ret['filaments'][typeID] = qty
    963             elif typeID in evetypes.GetTypeIDsByListID(TYPE_LIST_OTHER_CARGO_HOLD_TYPES):
    964                 ret['otherInCargo'][typeID] = qty
    965             else:
    966                 ret['other'][typeID] = qty
    967 
    968         return ret
    969 
    970     def HasSkillForFit(self, fitting):
    971         fittingID = fitting.fittingID
    972         try:
    973             return self.hasSkillByFittingID[fittingID]
    974         except KeyError:
    975             self.LogInfo('HasSkillForFit::Cache miss', fittingID)
    976             sys.exc_clear()
    977 
    978         hasSkill = self.hasSkillByFittingID[fittingID] = self.CheckSkillRequirementsForFit(fitting)
    979         return hasSkill
    980 
    981     def CheckSkillRequirementsForFit(self, fitting):
    982         godma = sm.GetService('godma')
    983         if not godma.CheckSkillRequirementsForType(fitting.shipTypeID):
    984             return False
    985         for typeID, flag, qty in fitting.fitData:
    986             if flag in inventorycommon.const.rigSlotFlags:
    987                 continue
    988             if not godma.CheckSkillRequirementsForType(typeID):
    989                 return False
    990 
    991         return True
    992 
    993     def GetAllFittings(self):
    994         ret = {}
    995         charFittings = self.GetFittings(session.charid)
    996         corpFittings = self.GetFittings(session.corpid)
    997         for fittingID in charFittings:
    998             ret[fittingID] = charFittings[fittingID]
    999 
   1000         for fittingID in corpFittings:
   1001             ret[fittingID] = corpFittings[fittingID]
   1002 
   1003         if session.allianceid:
   1004             allianceFittings = self.GetFittings(session.allianceid)
   1005             for fittingID, fitting in allianceFittings.iteritems():
   1006                 ret[fittingID] = fitting
   1007 
   1008         return ret
   1009 
   1010     def OnSkillsChanged(self, *args):
   1011         self.hasSkillByFittingID = {}
   1012 
   1013     def OnFittingAdded(self, ownerID, fitID):
   1014         if ownerID in self.fittings:
   1015             deleteFits = False
   1016             if isinstance(fitID, (int, long)):
   1017                 if fitID not in self.fittings[ownerID]:
   1018                     deleteFits = True
   1019             elif isinstance(fitID, list):
   1020                 if any((x in self.fittings[ownerID] for x in fitID)):
   1021                     deleteFits = True
   1022             else:
   1023                 raise RuntimeError("fitID should always be an int, long, or list. It wasn't. fitID = {} and type(fitID) = {}".format(fitID, type(fitID)))
   1024             if deleteFits is True:
   1025                 del self.fittings[ownerID]
   1026                 self.UpdateFittingWindow()
   1027                 sm.ScatterEvent('OnFittingsUpdated')
   1028 
   1029     def OnFittingDeleted(self, ownerID, fitID):
   1030         if ownerID in self.fittings:
   1031             removed = self._OnFittingDeleted(ownerID, fitID)
   1032             if removed:
   1033                 self.UpdateFittingWindow()
   1034                 sm.ScatterEvent('OnFittingsUpdated')
   1035 
   1036     def _OnFittingDeleted(self, ownerID, fitID):
   1037         if fitID in self.fittings[ownerID]:
   1038             del self.fittings[ownerID][fitID]
   1039             return True
   1040         return False
   1041 
   1042     def OnManyFittingsDeleted(self, ownerID, fitIDs):
   1043         if ownerID not in self.fittings:
   1044             return
   1045         numRemoved = 0
   1046         for fitID in fitIDs:
   1047             removed = self._OnFittingDeleted(ownerID, fitID)
   1048             numRemoved += removed
   1049 
   1050         if numRemoved:
   1051             self.UpdateFittingWindow()
   1052             sm.ScatterEvent('OnFittingsUpdated')
   1053 
   1054     def OnFittingsUpdated(self, fitting = None):
   1055         if not self.IsShipSimulated() or fitting is None:
   1056             return
   1057         shipTypeID, fittingDataInWnd, allItems = self.GetFittingDictForSimulatedShip(putModuleAmmoInCargo=True)
   1058         if fitting.shipTypeID != shipTypeID:
   1059             return
   1060         savedData = sorted(fitting.fitData)
   1061         currentData = sorted(fittingDataInWnd)
   1062         if savedData == currentData:
   1063             sm.GetService('ghostFittingSvc').ResetSimulationChangedFlag()
   1064 
   1065     def ImportFittingFromClipboard(self, simulate = False, *args):
   1066         try:
   1067             textInField = GetClipboardData()
   1068             shipName, fitName = FindShipAndFittingName(textInField)
   1069             importFittingUtil = self.GetImportFittingUtil()
   1070             shipTypeID = importFittingUtil.GetTypeIDFromName(shipName.lower())
   1071             if not shipTypeID:
   1072                 prompt_player_fsd_dialog('ImportingErrorFromClipboard')
   1073                 return
   1074             itemLines, errorLines = importFittingUtil.GetAllItems(textInField)
   1075             fitData = importFittingUtil.CreateFittingData(itemLines, shipTypeID)
   1076             if not fitData:
   1077                 prompt_player_fsd_dialog('ImportingErrorFromClipboard')
   1078                 return
   1079             fittingData = utillib.KeyVal(shipTypeID=shipTypeID, fitData=fitData, fittingID=None, description='', name=fitName, ownerID=0)
   1080             if simulate:
   1081                 sm.GetService('ghostFittingSvc').SimulateFitting(fittingData)
   1082             else:
   1083                 self.DisplayFitting(fittingData)
   1084             if errorLines:
   1085                 errorText = '<br>'.join(errorLines)
   1086                 text = '<b>%s</b><br>%s' % (localization.GetByLabel('UI/SkillQueue/CouldNotReadLines'), errorText)
   1087                 prompt_player_fsd_dialog('CustomInfo', {'info': text})
   1088         except Exception as e:
   1089             log.LogWarn('Failed to import fitting from clipboard, e = ', e)
   1090             prompt_player_fsd_dialog('ImportingErrorFromClipboard')
   1091 
   1092     def GetImportFittingUtil(self):
   1093         if self.importFittingUtil is None:
   1094             self.importFittingUtil = ImportFittingUtil(dogma.data.get_all_type_effects(), sm.GetService('clientDogmaStaticSvc'), IsUsingDefaultLanguage(session))
   1095         return self.importFittingUtil
   1096 
   1097     def ExportFittingToClipboard(self, fitting, isLocalized = False):
   1098         fittingString = GetFittingEFTString(fitting, isLocalized=isLocalized)
   1099         blue.pyos.SetClipboardData(fittingString)
   1100 
   1101     def SaveFitting(self, *args):
   1102         return self.DoSaveFitting()
   1103 
   1104     def DoSaveFitting(self, name = None):
   1105         fitting = self.GetFittingForCurrentInWnd()
   1106         if name:
   1107             fitting.name = name
   1108         windowID = 'Save_ViewFitting_%s' % fitting.shipTypeID
   1109         wnd = ViewFitting.GetIfOpen(windowID=windowID)
   1110         if wnd and not wnd.destroyed:
   1111             wnd.ReloadWnd(windowID, fitting, truncated=None)
   1112             wnd.Maximize()
   1113         else:
   1114             ViewFitting.Open(windowID=windowID, fitting=fitting, truncated=None)
   1115 
   1116     def GetFittingForCurrentInWnd(self, putModuleAmmoInCargo = True):
   1117         fitting = KeyVal()
   1118         fitting.shipTypeID, fitting.fitData, _ = self.GetFittingDictForCurrentFittingWindowShip(putModuleAmmoInCargo=putModuleAmmoInCargo)
   1119         ghostFittingSvc = sm.GetService('ghostFittingSvc')
   1120         desc = ''
   1121         if self.inSimulation:
   1122             ownerID = ghostFittingSvc.lastLoadedFitInfo[0]
   1123             fittingID = ghostFittingSvc.lastLoadedFitInfo[1]
   1124             if ownerID and fittingID:
   1125                 originalFit = self.GetFitting(ownerID, fittingID)
   1126                 if originalFit:
   1127                     desc = originalFit['description']
   1128         fitting.fittingID = None
   1129         fitting.description = desc
   1130         if self.inSimulation:
   1131             shipName = ghostFittingSvc.GetShipName()
   1132         else:
   1133             shipName = cfg.evelocations.Get(GetActiveShip()).locationName
   1134         fitting.name = shipName
   1135         fitting.ownerID = 0
   1136         return fitting
   1137 
   1138     def GetShipIDForFittingWindow(self):
   1139         if self.IsShipSimulated():
   1140             fittingDL = self.GetCurrentDogmaLocation()
   1141             return fittingDL.GetCurrentShipID()
   1142         return eveCfg.GetActiveShip()
   1143 
   1144     def GetCurrentDogmaLocation(self):
   1145         if self.IsShipSimulated():
   1146             return sm.GetService('clientDogmaIM').GetFittingDogmaLocation()
   1147         else:
   1148             return sm.GetService('clientDogmaIM').GetDogmaLocation()
   1149 
   1150     def IsShipSimulated(self):
   1151         return self.inSimulation
   1152 
   1153     def SetSimulationState(self, simulationOn = False):
   1154         ghostFittingController = sm.GetService('ghostFittingSvc').GetGhostFittingController()
   1155         if ghostFittingController:
   1156             ghostFittingController.SetSimulationState(simulationOn)
   1157         self.inSimulation = simulationOn
   1158 
   1159     def RemoveAndLoadChargesFromSimulatedShip(self, clientDL, actualShipItemID, simulatedChargeTypesAndQtyByFlagID):
   1160         actualShipInv = sm.GetService('invCache').GetInventoryFromId(actualShipItemID)
   1161         visibleFittedItems = [ i for i in actualShipInv.List() if inventorycommon.ItemIsVisible(i) ]
   1162         actualShip_chargeTypesAndQtyByFlagID = self.GetChargesAndQtyByFlag(visibleFittedItems)
   1163         if actualShip_chargeTypesAndQtyByFlagID == simulatedChargeTypesAndQtyByFlagID:
   1164             return {}
   1165         moduleIDs = {i.itemID for i in visibleFittedItems if i.flagID in inventorycommon.const.moduleSlotFlags and i.categoryID != const.categoryCharge}
   1166         moduleIDs = {i for i in moduleIDs if not clientDL.IsModuleSlave(i, actualShipItemID)}
   1167         clientDL.UnloadAmmoFromModules(actualShipItemID, moduleIDs, (session.structureid or session.stationid, session.charid, const.flagHangar))
   1168         blue.pyos.synchro.Yield()
   1169         return self.FitSimulatedCharges(clientDL, simulatedChargeTypesAndQtyByFlagID)
   1170 
   1171     def FitSimulatedCharges(self, clientDL, simulated_chargeTypesAndQtyByFlagID):
   1172         fitted = clientDL.GetShip().GetFittedItems()
   1173         fittedByFlagID = {x.flagID:x for x in fitted.itervalues() if x.categoryID != const.categoryCharge}
   1174         hangar = sm.GetService('invCache').GetInventory(const.containerHangar)
   1175         invItemsByTypeIDs = defaultdict(list)
   1176         for x in hangar.List(const.flagHangar):
   1177             invItemsByTypeIDs[x.typeID].append(x)
   1178 
   1179         ammoFailedToLoad = defaultdict(int)
   1180         for flagID, chargeInfo in simulated_chargeTypesAndQtyByFlagID.iteritems():
   1181             chargeTypeID, qty = chargeInfo
   1182             moduleItem = fittedByFlagID.get(flagID, None)
   1183             if not moduleItem:
   1184                 continue
   1185             invItemsForType = invItemsByTypeIDs.get(chargeTypeID, None)
   1186             if not invItemsForType:
   1187                 ammoFailedToLoad[chargeTypeID] += qty
   1188                 continue
   1189             try:
   1190                 moduleID = moduleItem.itemID
   1191                 if moduleID < invConst.flagSlotLast:
   1192                     log.LogTraceback('FitSimulatedCharges: ModuleID invalid')
   1193                 clientDL.LoadChargesToModule(moduleID, invItemsForType, invItemsForType[0].locationID)
   1194             except UserError as e:
   1195                 if e.msg == 'CannotLoadNotEnoughCharges':
   1196                     ammoFailedToLoad[chargeTypeID] += qty
   1197                 else:
   1198                     raise
   1199 
   1200         return ammoFailedToLoad
   1201 
   1202     def GetChargesAndQtyByFlag(self, fittedItems):
   1203         chargeTypesAndQtyByFlagID = defaultdict(int)
   1204         for item in fittedItems:
   1205             flagID = item.flagID
   1206             if item.categoryID == const.categoryCharge and flagID in const.moduleSlotFlags:
   1207                 stacksize = getattr(item, 'stacksize', 1)
   1208                 chargeTypesAndQtyByFlagID[flagID] = (item.typeID, stacksize)
   1209 
   1210         return chargeTypesAndQtyByFlagID
   1211 
   1212     def OnGlobalConfigChanged(self, configVals):
   1213         if not AreCommunityFittingsEnabled(sm.GetService('machoNet')):
   1214             self.ClearCommunityFittingCache()
   1215 
   1216     def OnCommunityFittingsUpdated(self):
   1217         self.ClearCommunityFittingCache()
   1218 
   1219     def ClearCommunityFittingCache(self):
   1220         if COMMUNITY_FITTING_CORP in self.fittings:
   1221             self.fittings.pop(COMMUNITY_FITTING_CORP)
   1222             sm.StartService('objectCaching').InvalidateCachedMethodCall('corpFittingMgr', 'GetCommunityFittings')