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

baseDogmaLocation.py (86279B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\common\script\dogma\baseDogmaLocation.py
      2 import math
      3 import sys
      4 import weakref
      5 from collections import defaultdict
      6 from dogma.const import dgmOperatorIDtoIndex
      7 from dogma.eventCounters import EventCount, BrainUpdate
      8 from dogma.dogmaLogging import *
      9 import blue
     10 import bluepy
     11 import dynamicitemattributes
     12 import evetypes
     13 import stackless
     14 import telemetry
     15 import uthread
     16 import utillib
     17 from carbon.common.script.sys.basesession import FindSessionClient
     18 from ccpProfile import TimedFunction
     19 from dogma.effects.environment import Environment
     20 from dogma.items.notWantedDogmaItem import NotWantedDogmaItem
     21 from dogma.items.dblessDogmaItem import DBLessDogmaItem
     22 from dogma.items.shipDogmaItem import ShipDogmaItem
     23 from dogma.items.probeDogmaItem import ProbeDogmaItem
     24 from dogma.items.chargeDogmaItem import ChargeDogmaItem
     25 from dogma.items.moduleDogmaItem import ModuleDogmaItem
     26 from dogma.items.controlTowerDogmaItem import ControlTowerDogmaItem
     27 from dogma.items.starbaseDogmaItem import StarbaseDogmaItem
     28 from dogma.items.droneDogmaItem import DroneDogmaItem
     29 from dogma.items.structureDogmaItem import StructureDogmaItem
     30 from dogma.items.structureModuleDogmaItem import StructureModuleDogmaItem
     31 from dogma.items.baseDogmaItem import BaseDogmaItem
     32 from dogma.items.characterDogmaItem import CharacterDogmaItem
     33 from dogma.items.fighterDogmaItem import FighterDogmaItem
     34 from dogma.exceptions import EmbarkOnlineError, EffectFailedButShouldBeStopped
     35 from dogma.attributes import BrainLiteralAttribute
     36 from eve.common.script.dogma.effect import ApplyBrainEffect, RemoveBrainEffect
     37 from eve.common.script.util.inventoryUtil import GetComputedBasePrice
     38 from eve.common.script.util.industryCommon import CHARACTER_MODIFIERS
     39 from eveexceptions import UserError
     40 from inventorycommon.const import categoryShip, categoryStructure, serviceSlotFlags, DEPRECATED_POS_MODULE_GROUPS
     41 from inventorycommon.util import IsShipFittingFlag, IsFittingModule, IsSubsystemFlag
     42 from logging import getLogger
     43 from utillib import keydefaultdict
     44 logger = getLogger('dogmaLocation')
     45 import log
     46 
     47 def OpName(opIdx):
     48     try:
     49         return opDecoder[opIdx]
     50     except KeyError:
     51         return opIdx
     52 
     53 
     54 opDecoder = {None: '..',
     55  const.dgmAssPreAssignment: '=.',
     56  const.dgmAssPreMul: '*.',
     57  const.dgmAssPreDiv: '/.',
     58  const.dgmAssModAdd: ' +',
     59  const.dgmAssModSub: ' -',
     60  const.dgmAssPostMul: '.*',
     61  const.dgmAssPostDiv: './',
     62  const.dgmAssPostPercent: '.%',
     63  const.dgmAssPostAssignment: '.=',
     64  const.dgmAssSpecialSkillRelationship: '.!',
     65  const.dgmAssPostPercentUnNerfed: '.%'}
     66 CATEGORY_SHIPS_AND_STRUCTURES = {categoryShip, categoryStructure}
     67 PROBE_GROUPS = (const.groupSurveyProbe,
     68  const.groupWarpDisruptionProbe,
     69  const.groupBurstEffectProbe,
     70  const.groupScannerProbe,
     71  const.groupBomb,
     72  const.groupBombECM,
     73  const.groupBombEnergy)
     74 
     75 class MessageMgr(object):
     76 
     77     def __init__(self):
     78         self.attributes = set()
     79 
     80     def AddAttribute(self, attr):
     81         self.attributes.add(attr)
     82 
     83     def GetMessages(self):
     84         messages = []
     85         for each in self.attributes:
     86             msg = each.ConstructMessage()
     87             if msg is not None:
     88                 messages.append(msg)
     89 
     90         return messages
     91 
     92     def Clear(self):
     93         self.attributes.clear()
     94 
     95 
     96 class BaseDogmaLocation(object):
     97 
     98     def __init__(self, broker):
     99         self.broker = broker
    100         self.onlineEffects = {const.effectOnline: True,
    101          const.effectOnlineForStructures: True}
    102         self.shipsByPilotID = {}
    103         self.pilotsByShipID = {}
    104         self.dogmaItems = {}
    105         self.locationID = None
    106         self.locationName = None
    107         self.locationGroup = const.groupSystem
    108         self.moduleListsByShipGroup = defaultdict(lambda : defaultdict(set))
    109         self.moduleListsByShipType = defaultdict(lambda : defaultdict(set))
    110         self.attributeChangeCallbacksByAttributeID = {}
    111         self.loadingItems = {}
    112         self.failedLoadingItems = {}
    113         self.crits = {}
    114         self.activatingEffects = {}
    115         self.deactivatingEffects = set()
    116         self.activatingEffectsByLocation = {}
    117         self.activatingEffectsByTarget = {}
    118         self.activeEffectsByShip = {}
    119         self.activeDungeonEffectsByItem = defaultdict(lambda : defaultdict(set))
    120         self.instanceFlagQuantityCache = {}
    121         self.unloadingItems = set()
    122         self.extraTracebacks = True
    123         self.pendingPowerCpuRequirementChecks = set()
    124         self.ignoredOwnerEvents = EventCount()
    125         self.itemsMissingLocation = defaultdict(set)
    126         self.itemsMissingOwner = defaultdict(set)
    127         self.modifiersAwaitingTarget = defaultdict(set)
    128         self.slaveModulesByMasterModule = {}
    129         self.onlineByShip = {}
    130         self.embarkingShips = {}
    131         self.instanceRowDescriptor = blue.DBRowDescriptor((('instanceID', const.DBTYPE_I8),
    132          ('online', const.DBTYPE_BOOL),
    133          ('damage', const.DBTYPE_R5),
    134          ('charge', const.DBTYPE_R5),
    135          ('skillPoints', const.DBTYPE_I4),
    136          ('armorDamage', const.DBTYPE_R5),
    137          ('shieldCharge', const.DBTYPE_R5),
    138          ('incapacitated', const.DBTYPE_BOOL)))
    139         l = [0] + [0] * (len(self.instanceRowDescriptor) - 1)
    140         self.fakeInstanceRow = blue.DBRow(self.instanceRowDescriptor, l)
    141         self.RegisterCallback(const.attributeCpuLoad, self.OnCpuAttributeChanged)
    142         self.RegisterCallback(const.attributePowerLoad, self.OnPowerAttributeChanged)
    143         self.RegisterCallback(const.attributeMaxGroupOnline, self.OnMaxGroupOnlineChanged)
    144         self.msgMgr = MessageMgr()
    145         self.brainLiterals = keydefaultdict(BrainLiteralAttribute)
    146         self.scarecrows = {}
    147         self.brainUpdate = BrainUpdate(self.CheckPowerCpuRequirementForOnlineModules)
    148 
    149     def LoadItem(self, itemKey, instanceRow = None, wait = False, invItem = None, newInstance = False, timerName = None):
    150         if itemKey == self.locationID:
    151             return itemKey
    152         if self.IsItemLoaded(itemKey):
    153             return itemKey
    154         self.EnterCriticalSection('LoadItem', itemKey)
    155         try:
    156             if itemKey in self.dogmaItems:
    157                 return itemKey
    158             self.loadingItems[itemKey] = stackless.channel()
    159             try:
    160                 dogmaItem = self._LoadItem(itemKey, instanceRow=instanceRow, invItem=invItem, newInstance=newInstance)
    161             finally:
    162                 while self.loadingItems[itemKey].queue:
    163                     self.loadingItems[itemKey].send(None)
    164 
    165                 del self.loadingItems[itemKey]
    166 
    167             if dogmaItem is not None:
    168                 if itemKey in self.modifiersAwaitingTarget:
    169                     LogNotice('LoadItem: Calling (and clearing) pending modifiers via itemKey {} for dogmaItem {}\n  modifiers = {}'.format(itemKey, dogmaItem, self.modifiersAwaitingTarget[itemKey]))
    170                     for callTuple in self.modifiersAwaitingTarget[itemKey]:
    171                         callTuple[0](*callTuple[1:])
    172 
    173                     del self.modifiersAwaitingTarget[itemKey]
    174                 dogmaItem.PostLoadAction()
    175         except Exception as e:
    176             log.LogException('Failed to load a dogma item %s' % str(itemKey))
    177             try:
    178                 if not len(e.args) or not e.args[0].startswith('GetItem: Item not here'):
    179                     self.failedLoadingItems[itemKey] = 'Exception: ' + strx(e)
    180             except Exception:
    181                 pass
    182 
    183             raise e
    184         finally:
    185             self.LeaveCriticalSection('LoadItem', itemKey)
    186 
    187         return itemKey
    188 
    189     def _LoadItem_LogOnEntry(self, itemKey, invItem):
    190         givenInvItemDescription = self.DescribeInvItem(invItem)
    191         foundDogmaItem = self.dogmaItems.get(itemKey, None)
    192         foundInvItemDescription = self.DescribeInvItem(foundDogmaItem.invItem) if foundDogmaItem else None
    193         totalItems = len(self.dogmaItems)
    194         LogNotice('LoadItem requested for itemKey: {itemKey}, with given invItem {givenInvItemDescription}, having existing dogmaItems entry {foundDogmaItem} with invItem {foundInvItemDescription} (totalItems = {totalItems}) '.format(**locals()))
    195 
    196     def _LoadItem_LogOnExit(self, itemKey):
    197         dogmaItem = self.dogmaItems.get(itemKey, None)
    198         invItemDescription = self.DescribeInvItem(dogmaItem.invItem) if dogmaItem else None
    199         LogNotice('LoadItem completed for itemKey: {itemKey}, having dogmaItems entry {dogmaItem} and invItem {invItemDescription}'.format(**locals()))
    200 
    201     def GetQuantityFromCache(self, locationID, flagID):
    202         return self.instanceFlagQuantityCache[locationID][flagID].quantity
    203 
    204     @TimedFunction('BaseDogmaLocation::_InstantiateDogmaItem')
    205     def _InstantiateDogmaItem(self, itemKey, invItem, instanceRow, newInstance):
    206         self.LogInfo('BaseDogmaLocation::_InstantiateDogmaItem', itemKey)
    207         if isinstance(itemKey, tuple):
    208             dogmaItemClass = DBLessDogmaItem
    209             invItem = utillib.KeyVal()
    210             invItem.itemID = itemKey
    211             invItem.locationID, invItem.flagID, invItem.typeID = itemKey
    212             invItem.groupID = evetypes.GetGroupID(invItem.typeID)
    213             invItem.categoryID = evetypes.GetCategoryID(invItem.typeID)
    214             invItem.ownerID = self.dogmaItems[invItem.locationID].ownerID
    215         else:
    216             if invItem is None:
    217                 invItem = self.GetItem(itemKey)
    218             try:
    219                 typeID = invItem.typeID
    220             except AttributeError:
    221                 raise RuntimeError('Dogma was unable to get invItem to load dogmaItem!')
    222 
    223             if not self.IsItemWanted(typeID) or newInstance:
    224                 instanceRow = self.IntroduceNewItem(invItem)
    225             else:
    226                 instanceRow = self.GetInstance(invItem)
    227             dogmaItemClass = self.GetClassForItem(invItem)
    228         dogmaItem = dogmaItemClass(weakref.proxy(self), invItem, cfg, FindSessionClient)
    229         return (dogmaItem, invItem, instanceRow)
    230 
    231     @TimedFunction('BaseDogmaLocation::_LoadItem')
    232     def _LoadItem(self, itemKey, instanceRow = None, invItem = None, newInstance = False):
    233         dogmaItem, invItem, instanceRow = self._InstantiateDogmaItem(itemKey, invItem, instanceRow, newInstance)
    234         self.LogInfo('BaseDogmaLocation::Actually Loading dogma item', itemKey)
    235         dogmaItem.Load(invItem, instanceRow)
    236         if invItem.flagID != const.flagPilot:
    237             self.FitItemToLocation(invItem.locationID, itemKey, invItem.flagID)
    238         dogmaItem.OnItemLoaded()
    239         return dogmaItem
    240 
    241     def GetDogmaItem(self, itemID):
    242         return self.dogmaItems[itemID]
    243 
    244     def SafeGetDogmaItem(self, itemID):
    245         return self.dogmaItems.get(itemID, None)
    246 
    247     def CheckApplyBrainEffects(self, shipID):
    248         pass
    249 
    250     def SetBrainData(self, charID, brain):
    251         pass
    252 
    253     def GetBrainData(self, charID):
    254         pass
    255 
    256     def InitBrain(self, charID, brain = None):
    257         pass
    258 
    259     def GetEmptyBrain(self):
    260         return (-1,
    261          [],
    262          [],
    263          [])
    264 
    265     @telemetry.ZONE_METHOD
    266     def GetModuleListByShipGroup(self, locationID, groupID):
    267         return self.moduleListsByShipGroup.get(locationID, {}).get(groupID, [])
    268 
    269     @telemetry.ZONE_METHOD
    270     def GetModuleListByShipType(self, locationID, typeID):
    271         return self.moduleListsByShipType.get(locationID, {}).get(typeID, [])
    272 
    273     def HandleDogmaLocationEffectsOnItem(self, dogmaItem):
    274         pass
    275 
    276     def ApplyBrainEffects(self, shipID, charID, timerName = 'unknown'):
    277         self.broker.LogInfo('%s ApplyBrainEffects %s %s. Called from %s' % (self.locationName,
    278          shipID,
    279          charID,
    280          timerName))
    281         if shipID is None:
    282             self.broker.LogError('ApplyBrainEffects - shipID is None')
    283         if charID is None:
    284             self.broker.LogError('ApplyBrainEffects - charID is None!')
    285             return
    286         startTime = blue.os.GetWallclockTimeNow()
    287         currentShipID = self.shipsByPilotID.get(charID, None)
    288         if shipID != currentShipID:
    289             self.shipsByPilotID[charID] = shipID
    290         currentPilotID = self.pilotsByShipID.get(shipID, None)
    291         if charID != currentPilotID:
    292             self.pilotsByShipID[shipID] = charID
    293         if not self.CheckApplyBrainEffects(shipID):
    294             endTime = blue.os.GetWallclockTimeNow()
    295             self.broker.LogInfo('%s - ApplyBrainEffects took %s ms' % (self.locationID, blue.os.TimeDiffInMs(startTime, endTime)))
    296             return
    297         log.LogInfo('%s - ApplyBrainEffects to shipID %s' % (self.locationID, shipID))
    298         shipDogmaItem = self.dogmaItems.get(shipID, None)
    299         if not shipDogmaItem:
    300             self.LogError('ApplyBrainEffects:ShipDogmaItem not found!')
    301             return
    302         _, charEffects, shipEffects, structureEffects = self.GetBrainData(charID)
    303         self._UpdateShipEffectsFromBrain(ApplyBrainEffect, 'Apply', shipEffects, structureEffects, shipID, shipDogmaItem)
    304         self._UpdateCharacterEffectsFromBrain(ApplyBrainEffect, 'Apply', charEffects, charID)
    305         self._ApplyPilotSecurityStatusToShip(shipID, charID)
    306         endTime = blue.os.GetWallclockTimeNow()
    307         self.broker.LogInfo('%s - ApplyBrainEffects took %s ms' % (self.locationID, blue.os.TimeDiffInMs(startTime, endTime)))
    308 
    309     def RemoveBrainEffects(self, shipID, charID, timerName = None):
    310         self.broker.LogInfo('%s RemoveBrainEffects %s %s. Called from %s' % (self.locationName,
    311          shipID,
    312          charID,
    313          timerName))
    314         if charID is None:
    315             return
    316         startTime = blue.os.GetWallclockTimeNow()
    317         _, charEffects, shipEffects, structureEffects = self.GetBrainData(charID)
    318         if shipID is not None:
    319             shipDogmaItem = self.dogmaItems.get(shipID, None)
    320             if not shipDogmaItem:
    321                 self.LogError('RemoveBrainEffects:ShipDogmaItem not found!')
    322                 return
    323             self._UpdateShipEffectsFromBrain(RemoveBrainEffect, 'Remove', shipEffects, structureEffects, shipID, shipDogmaItem)
    324             self._ApplyPilotSecurityStatusToShip(shipID, None)
    325         self.broker.LogInfo('Removing %s character effects from %s' % (len(charEffects), charID))
    326         for effect in charEffects:
    327             literal = self.brainLiterals[effect.GetLiteralKey()]
    328             effType = effect.modifierType
    329             RemoveBrainEffect[effType](self, charID, literal, *effect.GetApplicationArgs())
    330 
    331         endTime = blue.os.GetWallclockTimeNow()
    332         self.broker.LogInfo('%s RemoveBrainEffects took %s ms' % (self.locationID, blue.os.TimeDiffInMs(startTime, endTime)))
    333 
    334     def OnCharacterEmbarkation(self, charID, shipID, switching = False, raiseOnOnlineFailure = False):
    335         self.LogInfo('OnCharacterEmbarkation charID', charID, 'shipID', shipID)
    336         self.embarkingShips[shipID] = charID
    337         try:
    338             self._OnCharacterEmbarkation(charID, shipID, switching, raiseOnOnlineFailure)
    339         finally:
    340             if shipID in self.embarkingShips:
    341                 del self.embarkingShips[shipID]
    342 
    343     def _OnCharacterEmbarkation(self, charID, shipID, switching, raiseOnOnlineFailure):
    344         if self.shipsByPilotID.get(charID, None) == shipID:
    345             self.LogError('OnCharacterEmbarkation - character already embarked? Are we doing things out of order?', charID, shipID, switching)
    346             return
    347         if not self.IsItemLoaded(charID):
    348             self.LoadItem(charID)
    349         if charID in self.shipsByPilotID:
    350             skipCheck = False
    351             oldShipID = self.shipsByPilotID.get(charID, None)
    352         else:
    353             skipCheck = True
    354             oldShipID = None
    355         if oldShipID == shipID:
    356             self.LogError("OnCharacterEmbarkation - character seem to have been already fitted to this ship so I'm returning", charID, shipID, switching)
    357             return
    358         if not self.IsItemLoaded(shipID):
    359             self.LoadItem(shipID)
    360         shipDogmaItem = self.dogmaItems[shipID]
    361         charDogmaItem = self.dogmaItems[charID]
    362         charDogmaItem.SetLocation(shipID, shipDogmaItem, const.flagPilot)
    363         shipFittedItems = shipDogmaItem.GetFittedItems()
    364         if self.extraTracebacks:
    365             self.LogInfo('Embark, sorting modules and ship', shipID, shipFittedItems.keys())
    366         for itemKey in shipFittedItems:
    367             self.InstallPilotForItem(itemKey, shipID, charID)
    368 
    369         for droneID in shipDogmaItem.GetDronesInBay():
    370             if droneID not in self.dogmaItems:
    371                 self.LogError("drone in ship's drone dict but not actually loadedd", droneID, charID, shipID)
    372                 continue
    373             self.InstallPilotForItem(droneID, shipID, charID)
    374 
    375         if self.extraTracebacks:
    376             self.LogInfo('Embark, sorted modules and ship', shipID, shipFittedItems.keys())
    377         self.broker.LogInfo('OnCharacterEmbarkation - char adoption', charID)
    378         self.LogInfo('OnCharacterEmbarkation - char', charID, 'reonlining modules on ship', shipID)
    379         self.ApplyBrainEffects(shipID, charID, 'baseDogmaLocation._OnCharacterEmbarkation')
    380         if shipID in self.onlineByShip:
    381             d = self.onlineByShip[shipID]
    382             del self.onlineByShip[shipID]
    383             self.LogInfo('OnCharacterEmbarkation - ship', shipID, 'had online cache hit', d)
    384             flags = d.keys()
    385             flags.sort()
    386             brokenByID = {}
    387             for i in range(len(flags) - 1, -1, -1):
    388                 moduleID = d[flags[i]]
    389                 try:
    390                     self.StopEffect(const.effectOnline, moduleID, 0)
    391                 except UserError as e:
    392                     brokenByID[moduleID] = True
    393                     item = self.inventory2.GetItem(moduleID)
    394                     self.broker.LogError('Char', charID, 'piloting', shipID, 'module', moduleID, evetypes.GetName(item.typeID), 'not offline, reason', e)
    395                     log.LogException(channel='svc.dogmaIM')
    396                     sys.exc_clear()
    397 
    398             for flag in flags:
    399                 moduleID = d[flag]
    400                 if moduleID not in brokenByID:
    401                     try:
    402                         self.StartModuleOnlineEffect(moduleID, charID, shipID, raiseUserError=raiseOnOnlineFailure)
    403                     except UserError as e:
    404                         if raiseOnOnlineFailure:
    405                             log.LogException('Failed to online modules on undock', severity=log.LGWARN, channel='svc.dogmaIM')
    406                             raise EmbarkOnlineError('FailedToOnlineModulesOnUndock')
    407                         if e.msg != 'OnlineHasSkillPrerequisites':
    408                             item = self.inventory2.GetItem(moduleID)
    409                             self.broker.LogWarn('OnCharacterEmbarkation - char', charID, 'piloting', shipID, 'module', moduleID, evetypes.GetName(item.typeID), 'not onlined again, reason', e)
    410                             log.LogException(channel='svc.dogmaIM')
    411                         sys.exc_clear()
    412 
    413             self.LogInfo('OnCharacterEmbarkation - char', charID, 'reonlined modules on ship', shipID, 'candidates', d)
    414 
    415     def InstallPilotForItem(self, itemKey, shipID, pilotID):
    416         dogmaItem = self.dogmaItems[itemKey]
    417         itemTypeID = dogmaItem.typeID
    418         itemGroupID = evetypes.GetGroupID(itemTypeID)
    419         fittableNonSingleton = evetypes.GetIsGroupFittableNonSingletonByGroup(itemGroupID)
    420         targetID = shipID
    421         otherID = None
    422         aeff = dogmaItem.activeEffects
    423         for effectID in self.dogmaStaticMgr.GetPassiveFilteredEffectsByType(itemTypeID):
    424             if effectID in aeff:
    425                 continue
    426             if otherID is None and fittableNonSingleton:
    427                 otherID = self.GetSlotOther(shipID, dogmaItem.flagID)
    428             if otherID is None and self.effectCompiler.IsEffectDomainOther(effectID):
    429                 continue
    430             self.LogInfo('InstallPilotForItem.effect', itemKey, effectID, dogma_data.get_effect_name(effectID))
    431             try:
    432                 structureId = self.GetStructureIdForEnvironment(shipID)
    433                 env = Environment(itemKey, pilotID, shipID, targetID, otherID, effectID, weakref.proxy(self), None, structureId)
    434                 self.StartEffect(effectID, itemKey, env)
    435             except UserError as e:
    436                 sys.exc_clear()
    437                 ek, ea = e.msg, e.dict
    438                 if ek == 'ShipExploding':
    439                     self.LogInfo('InstallPilotForItem - ship', shipID, 'exploding, so module', itemKey, 'fit failed,  mid effect start loop', effectID)
    440                     return
    441 
    442     @TimedFunction('BaseDogmaLocation::FitItemToLocation')
    443     def FitItemToLocation(self, locationID, itemID, flagID):
    444         self.LogInfo('FitItemToLocation', locationID, itemID, flagID)
    445         if locationID == self.locationID:
    446             self.LogInfo('Attempting to fit item to self.locationID. Ignored!')
    447             return
    448         if locationID not in self.dogmaItems:
    449             wasItemLoaded = itemID in self.dogmaItems
    450             if locationID != self.locationID:
    451                 self.LoadItem(locationID)
    452             if not wasItemLoaded:
    453                 self.LogInfo('Neither location not item was loaded. Loaded location and returned.', locationID, itemID)
    454                 return
    455         if itemID not in self.dogmaItems:
    456             self.LoadItem(itemID)
    457             return
    458         locationDogmaItem = self.dogmaItems.get(locationID, None)
    459         if locationDogmaItem is None:
    460             self.LogInfo('FitItemToLocation::Fitted to None item', itemID, locationID, flagID)
    461             return
    462         dogmaItem = self.dogmaItems[itemID]
    463         if not locationDogmaItem.CanFitItem(dogmaItem, flagID):
    464             if locationDogmaItem.categoryID == categoryShip and IsShipFittingFlag(flagID):
    465                 logger.error("Somebody asked us to fit an item to a ship that can't be fitted")
    466             return
    467         oldInfo = dogmaItem.GetLocationInfo()
    468         dogmaItem.SetLocation(locationID, locationDogmaItem, flagID)
    469         startedEffects = self.StartPassiveEffects(itemID, dogmaItem.typeID)
    470         if startedEffects is not None:
    471             self.UnsetItemLocation(itemID, locationID)
    472             for effectID in startedEffects:
    473                 try:
    474                     self.StopEffect(effectID, itemID, 1)
    475                 except UserError as e:
    476                     log.LogException('Failed to start effect %s' % effectID, channel='svc.dogmaIM')
    477                     sys.exc_clear()
    478 
    479             if IsSubsystemFlag(dogmaItem.flagID):
    480                 raise RuntimeError('Failed to start passive effects on a subsystem')
    481             raise UserError('ModuleFitFailed', {'moduleName': (const.UE_GROUPID, dogmaItem.groupID),
    482              'reason': ''})
    483         locationCategoryID = locationDogmaItem.categoryID
    484         if locationCategoryID in CATEGORY_SHIPS_AND_STRUCTURES and dogmaItem.attributes.get(const.attributeIsOnline, False) and self.dogmaStaticMgr.TypeHasEffect(dogmaItem.typeID, const.effectOnline):
    485             pilotID = dogmaItem.GetPilot()
    486             self.StartModuleOnlineEffect(dogmaItem.itemID, pilotID, dogmaItem.locationID)
    487         return oldInfo
    488 
    489     @telemetry.ZONE_METHOD
    490     def _StartModuleOnlineEffect(self, itemID, pilotID, locationID, context = None, raiseUserError = False):
    491         environment = self.GetEnvironment(self.dogmaItems[itemID].GetEnvironmentInfo(), const.effectOnline)
    492         if context is not None:
    493             for k, v in context.iteritems():
    494                 setattr(environment, k, v)
    495 
    496         try:
    497             self.StartEffect(const.effectOnline, itemID, environment, 1, byUser=True)
    498         except UserError as e:
    499             if e.msg.startswith('EffectAlreadyActive'):
    500                 self.LogInfo('Tried to online', itemID, 'in', locationID, 'but failed because of', e)
    501             else:
    502                 self.SetAttributeValue(itemID, const.attributeIsOnline, 0)
    503             if raiseUserError:
    504                 raise
    505             sys.exc_clear()
    506 
    507     def StartModuleOnlineEffect(self, itemID, pilotID, locationID, context = None, raiseUserError = False):
    508         item = self.GetItem(itemID)
    509         if item.locationID != locationID or not IsShipFittingFlag(item.flagID):
    510             self.LogInfo('StartModuleOnlineEffect::Unexpected location', itemID, item.locationID, locationID)
    511             return
    512         if not self.dogmaStaticMgr.TypeHasEffect(item.typeID, const.effectOnline):
    513             logger.error('Cannot start module online effect as this type does not have the online effect', extra={'typeID': item.typeID})
    514             return
    515         if pilotID is not None or item.categoryID == const.categoryStructureModule:
    516             self._StartModuleOnlineEffect(itemID, pilotID, locationID, context=context, raiseUserError=raiseUserError)
    517             return
    518         if locationID not in self.onlineByShip:
    519             self.onlineByShip[locationID] = {}
    520         dogmaItem = self.dogmaItems[itemID]
    521         flagID = dogmaItem.flagID
    522         dogmaItem.attributes[const.attributeIsOnline].SetBaseValue(1)
    523         self.onlineByShip[locationID][flagID] = itemID
    524 
    525     def UnfitItemFromLocation(self, locationID, itemID, flushEffects = False):
    526         dogmaItem = self.dogmaItems[itemID]
    527         itemTypeID = dogmaItem.typeID
    528         self.UnsetItemLocation(itemID, locationID)
    529         for effectID, effect in dogmaItem.activeEffects.items():
    530             if type(effectID) is tuple:
    531                 effectID = effectID[0]
    532             if not flushEffects and effect[const.ACT_IDX_DURATION] != -1:
    533                 raise UserError('ModuleEffectActive', {'module': itemTypeID})
    534             if self.dogmaStaticMgr.effects[effectID].effectCategory != const.dgmEffPassive:
    535                 self.StopEffect(effectID, itemID, forced=flushEffects)
    536 
    537         self.StopPassiveEffects(dogmaItem, itemID, itemTypeID)
    538 
    539     def StopPassiveEffects(self, dogmaItem, itemID, itemTypeID):
    540         effectsCompleted = set()
    541         if itemTypeID not in self.dogmaStaticMgr.effectsByType:
    542             return
    543         for effectID in self.dogmaStaticMgr.TypeGetOrderedEffectIDs(itemTypeID):
    544             if effectID not in dogmaItem.activeEffects:
    545                 continue
    546             effect = self.dogmaStaticMgr.effects[effectID]
    547             if effect.effectCategory in [const.dgmEffPassive, const.dgmEffSystem]:
    548                 try:
    549                     self.StopEffect(effectID, itemID)
    550                     effectsCompleted.add(effectID)
    551                 except UserError as e:
    552                     for _effectID in effectsCompleted:
    553                         try:
    554                             info = dogmaItem.GetEnvironmentInfo()
    555                             env = self.GetEnvironment(info, _effectID)
    556                             self.StartEffect(_effectID, itemID, env)
    557                         except UserError as e:
    558                             log.LogException(channel='svc.dogmaIM')
    559                             sys.exc_clear()
    560 
    561                     sys.exc_clear()
    562 
    563     def UnsetItemLocation(self, itemKey, locationID):
    564         locationDogmaItem = self.dogmaItems.get(locationID, None)
    565         dogmaItem = self.dogmaItems[itemKey]
    566         if locationDogmaItem is None:
    567             self.LogError("UnsetItemLocation::called for location that wasn't loaded", itemKey, locationID)
    568             return
    569         dogmaItem.UnsetLocation(locationDogmaItem)
    570 
    571     @bluepy.TimedFunction('DogmaLocation::StartEffect')
    572     def StartEffect(self, effectID, itemKey, environment, repeat = 0, byUser = 0, checksOnly = None):
    573         dogmaItem = None
    574         try:
    575             dogmaItem = self.dogmaItems[itemKey]
    576         except KeyError:
    577             self.LogError('StartEffect::Item neither loaded nor being loaded', effectID, itemKey)
    578             self.LoadItem(itemKey)
    579             dogmaItem = self.dogmaItems[itemKey]
    580 
    581         activateKey = (itemKey, effectID)
    582         if effectID not in self.dogmaStaticMgr.effectsByType.get(dogmaItem.typeID, []):
    583             if effectID == const.effectOnline:
    584                 self.LogInfo("StartEffect::Online Effect being started on a type that doesn't have it", itemKey, dogmaItem.typeID, effectID)
    585             else:
    586                 self.LogError("StartEffect::Effect being started on type that doesn't have it (unexpected)", itemKey, dogmaItem.typeID, effectID)
    587                 if evetypes.GetGroupID(dogmaItem.typeID) in DEPRECATED_POS_MODULE_GROUPS:
    588                     raise TypeError('DeprecatedPosModule')
    589             return
    590         if dogmaItem.IsEffectRegistered(effectID, environment):
    591             raise UserError('EffectAlreadyActive2', {'modulename': (const.UE_TYPEID, dogmaItem.typeID)})
    592         if activateKey in self.activatingEffects:
    593             self.broker.LogWarn('Effect already activating o_O', activateKey, blue.os.TimeDiffInMs(self.activatingEffects[activateKey][0], blue.os.GetSimTime()) / 1000.0)
    594             raise UserError('EffectAlreadyActive2', {'modulename': (const.UE_TYPEID, dogmaItem.typeID)})
    595         self.activatingEffects[activateKey] = [blue.os.GetSimTime(), None, environment]
    596         itemLocationID = dogmaItem.locationID
    597         if itemLocationID not in self.activatingEffectsByLocation:
    598             self.activatingEffectsByLocation[itemLocationID] = set([activateKey])
    599         else:
    600             self.activatingEffectsByLocation[itemLocationID].add(activateKey)
    601         targetID = environment.targetID
    602         if targetID:
    603             if targetID not in self.activatingEffectsByTarget:
    604                 self.activatingEffectsByTarget[targetID] = set([activateKey])
    605             else:
    606                 self.activatingEffectsByTarget[targetID].add(activateKey)
    607         try:
    608             effect = self.dogmaStaticMgr.effects[effectID]
    609             self.StartEffect_PreChecks(effect, dogmaItem, environment, byUser)
    610             duration = self.GetEffectDuration(effect, environment)
    611             try:
    612                 effectStart = self._StartEffect(effect, dogmaItem, environment, duration, repeat, byUser, checksOnly)
    613             except EffectFailedButShouldBeStopped:
    614                 effectStart = blue.os.GetSimTime()
    615                 repeat = 0
    616                 sys.exc_clear()
    617 
    618             self.RegisterEffect(effect, dogmaItem, environment, effectStart, duration, repeat)
    619         finally:
    620             del self.activatingEffects[activateKey]
    621             self.activatingEffectsByLocation[itemLocationID].remove(activateKey)
    622             if not len(self.activatingEffectsByLocation[itemLocationID]):
    623                 del self.activatingEffectsByLocation[itemLocationID]
    624             if targetID:
    625                 self.activatingEffectsByTarget[targetID].remove(activateKey)
    626                 if not len(self.activatingEffectsByTarget[targetID]):
    627                     del self.activatingEffectsByTarget[targetID]
    628 
    629     def _StartEffect(self, effect, dogmaItem, environment, duration, repeat, byUser, checksOnly):
    630         effectID = effect.effectID
    631         self.effectCompiler.PreStartEffectChecks(effectID, environment)
    632         if effectID == const.effectOnline:
    633             for onlineEffectID in self.dogmaStaticMgr.effectsByType[dogmaItem.typeID]:
    634                 if self.dogmaStaticMgr.effects[onlineEffectID].effectCategory == const.dgmEffOnline:
    635                     self.effectCompiler.PreStartEffectChecks(onlineEffectID, environment)
    636 
    637         self.ConsumeResources(environment)
    638         itemKey = dogmaItem.itemID
    639         effectStart = blue.os.GetSimTime()
    640         environment.OnStart(effect, effectStart)
    641         if environment.startTime is None:
    642             environment.startTime = effectStart
    643         if checksOnly is None:
    644             targetID = environment.targetID
    645             flag = self.CheckApplyModifiers(environment.shipID, itemKey, effect, targetID, environment)
    646             if flag is not None:
    647                 checksOnly = not flag
    648         self.PossiblyStartOverloadingEffects(effect.effectCategory, effectID, environment.shipID, itemKey, dogmaItem.typeID, environment)
    649         if checksOnly:
    650             self.effectCompiler.StartEffectChecks(effectID, environment)
    651         else:
    652             self.effectCompiler.StartEffect(effectID, environment)
    653         if effectID == const.effectOnline:
    654             try:
    655                 for onlineEffectID in self.dogmaStaticMgr.effectsByType[dogmaItem.typeID]:
    656                     if self.dogmaStaticMgr.effects[onlineEffectID].effectCategory != const.dgmEffOnline:
    657                         continue
    658                     try:
    659                         self.StartEffect(onlineEffectID, itemKey, environment, repeat, 0)
    660                     except UserError:
    661                         self.LogError('Failed to start online effect', onlineEffectID, effectID, itemKey)
    662                         sys.exc_clear()
    663 
    664             except KeyError:
    665                 self.LogWarn('Onlining item with no online effects', itemKey)
    666 
    667         return effectStart
    668 
    669     def PossiblyStartOverloadingEffects(self, *args):
    670         pass
    671 
    672     def ConsumeResources(self, environment):
    673         pass
    674 
    675     def GetEffectDuration(self, effect, environment):
    676         if effect.durationAttributeID:
    677             duration = self.GetAttributeValue(environment.itemID, effect.durationAttributeID)
    678             if duration < 0:
    679                 self.FullyLogAttribute(environment.itemID, effect.durationAttributeID, '[EffectCantHaveNegativeDuration %s]' % effect.effectName, force=True)
    680                 raise UserError('EffectCantHaveNegativeDuration', {'modulename': (const.UE_TYPEID, environment.itemTypeID),
    681                  'duration': duration})
    682             elif duration < 20:
    683                 self.broker.LogInfo('Effect', effect.effectName, 'has very conspicuous short duration', duration)
    684         else:
    685             duration = -1
    686         return duration
    687 
    688     def RegisterEffect(self, effect, dogmaItem, environment, effectStart, duration, repeat):
    689         effectID = effect.effectID
    690         dogmaItem.RegisterEffect(effectID, environment.currentStartTime, duration, environment, repeat)
    691         if environment.shipID not in self.activeEffectsByShip:
    692             self.activeEffectsByShip[environment.shipID] = {effectID: dogmaItem.itemID}
    693         else:
    694             self.activeEffectsByShip[environment.shipID][effectID] = dogmaItem.itemID
    695 
    696     def StartEffect_PreChecks(self, effect, dogmaItem, environment, byUser):
    697         if byUser and effect.effectCategory in [const.dgmEffOnline, const.dgmEffOverload]:
    698             raise RuntimeError('TheseEffectsShouldNeverBeUserStarted', effect.effectID)
    699         if effect.effectID == const.effectOnline:
    700             moduleHp = self.GetAttributeValue(dogmaItem.itemID, const.attributeHp)
    701             moduleDamage = self.GetAttributeValue(dogmaItem.itemID, const.attributeDamage)
    702             if moduleHp and moduleHp <= moduleDamage:
    703                 raise UserError('ModuleTooDamagedToBeOnlined')
    704 
    705     def CheckApplyModifiers(self, *args):
    706         pass
    707 
    708     @bluepy.TimedFunction('DogmaLocation::StopEffect')
    709     @telemetry.ZONE_METHOD
    710     def StopEffect(self, effectID, itemKey, byUser = False, activationInfo = None, forced = False, forceStopRepeating = False, possiblyStopRepeat = True):
    711         try:
    712             dogmaItem = self.dogmaItems[itemKey]
    713         except KeyError:
    714             self.LogInfo("StopEffect called on an item that isn't loaded", itemKey, effectID)
    715             return
    716 
    717         deactivateKey = (itemKey, effectID)
    718         if deactivateKey in self.deactivatingEffects:
    719             self.LogWarn('Multiple concurrent StopEffect calls for', deactivateKey)
    720             return
    721         self.deactivatingEffects.add(deactivateKey)
    722         try:
    723             if activationInfo is None:
    724                 try:
    725                     activationInfo = dogmaItem.activeEffects[effectID]
    726                 except KeyError:
    727                     if (itemKey, effectID) in self.activatingEffects:
    728                         raise UserError('EffectStillActivating', {'moduleName': (const.UE_TYPEID, dogmaItem.typeID)})
    729                     self.LogInfo("Stopping effect that isn't active", itemKey, effectID)
    730                     return 0
    731 
    732             if not self.PreStopEffectChecks(effectID, dogmaItem, activationInfo, forced, byUser, possiblyStopRepeat):
    733                 return
    734             error = self._StopEffect(effectID, dogmaItem, activationInfo, byUser, forced, forceStopRepeating, possiblyStopRepeat)
    735             self.PostStopEffectAction(effectID, dogmaItem, activationInfo, forced, byUser, error)
    736         finally:
    737             self.deactivatingEffects.remove(deactivateKey)
    738 
    739     @telemetry.ZONE_METHOD
    740     def _StopEffect(self, effectID, dogmaItem, activationInfo, byUser, forced, forceStopRepeating, possiblyStopRepeat):
    741         if effectID in self.onlineEffects:
    742             effects = dogmaItem.activeEffects
    743             itemKey = dogmaItem.itemID
    744             for otherEffectID, activationInfo2 in effects.items():
    745                 if otherEffectID != effectID and otherEffectID in effects:
    746                     otherDuration = effects[otherEffectID][const.ACT_IDX_DURATION]
    747                     if otherDuration == -1 and self.dogmaStaticMgr.effects[otherEffectID].effectCategory != const.dgmEffPassive:
    748                         self.StopEffect(otherEffectID, itemKey, False, activationInfo2)
    749 
    750         environment = activationInfo[const.ACT_IDX_ENV]
    751         self.effectCompiler.StopEffect(effectID, environment, forced)
    752 
    753     def PreStopEffectChecks(self, effectID, dogmaItem, activationInfo, forced, byUser, possiblyStopRepeat, *args):
    754         return True
    755 
    756     @telemetry.ZONE_METHOD
    757     def PostStopEffectAction(self, effectID, dogmaItem, activationInfo, forced, byUser, error):
    758         environment = activationInfo[const.ACT_IDX_ENV]
    759         if dogmaItem.IsEffectRegistered(effectID, environment):
    760             effect = self.dogmaStaticMgr.effects[effectID]
    761             self.UnregisterEffect(effect, dogmaItem, environment)
    762 
    763     def UnregisterEffect(self, effect, dogmaItem, environment):
    764         effectID = effect.effectID
    765         dogmaItem.UnregisterEffect(effectID, environment)
    766         shipID = environment.shipID
    767         try:
    768             if shipID in self.activeEffectsByShip:
    769                 if effectID in self.activeEffectsByShip[shipID]:
    770                     del self.activeEffectsByShip[shipID][effectID]
    771                 if len(self.activeEffectsByShip[shipID]) == 0:
    772                     del self.activeEffectsByShip[shipID]
    773             if shipID in self.activeDungeonEffectsByItem:
    774                 beaconID = environment.itemID
    775                 if effectID in self.activeDungeonEffectsByItem[shipID]:
    776                     try:
    777                         self.activeDungeonEffectsByItem[shipID][effectID].remove(beaconID)
    778                     except ValueError:
    779                         pass
    780 
    781                     if len(self.activeDungeonEffectsByItem[shipID][effectID]) == 0:
    782                         del self.activeDungeonEffectsByItem[shipID][effectID]
    783                 if len(self.activeDungeonEffectsByItem[shipID]) == 0:
    784                     del self.activeDungeonEffectsByItem[shipID]
    785         except KeyError:
    786             sys.exc_clear()
    787 
    788     @telemetry.ZONE_METHOD
    789     def StartPassiveEffects(self, itemID, typeID):
    790         effectIDList = self.dogmaStaticMgr.GetPassiveFilteredEffectsByType(typeID)
    791         if not effectIDList:
    792             self.LogInfo('No effects to start for item %s of type %s' % (itemID, evetypes.GetName(typeID)))
    793             return
    794         dogmaItem = self.dogmaItems[itemID]
    795         brokerEffects = self.dogmaStaticMgr.effects
    796         pilotID = dogmaItem.GetPilot()
    797         shipID = dogmaItem.GetShip()
    798         sharedEnv = None
    799         effectsCompleted = set()
    800         effectID = None
    801         try:
    802             for effectID in effectIDList:
    803                 if pilotID is None and self.effectCompiler.IsEffectCharacterModifier(effectID):
    804                     continue
    805                 if shipID is None and self.effectCompiler.IsEffectShipModifier(effectID):
    806                     continue
    807                 if effectID in dogmaItem.activeEffects:
    808                     continue
    809                 chanceAttributeID = brokerEffects[effectID].fittingUsageChanceAttributeID
    810                 if chanceAttributeID and self.dogmaStaticMgr.TypeHasAttribute(typeID, chanceAttributeID):
    811                     if not self.ShouldStartChanceBasedEffect(effectID, itemID, chanceAttributeID):
    812                         continue
    813                 if self.dogmaStaticMgr.effects[effectID].effectCategory == const.dgmEffSystem:
    814                     self.StartSystemEffect(itemID, effectID)
    815                     continue
    816                 pythonEffect = self.effectCompiler.IsEffectPythonOverridden(effectID)
    817                 if sharedEnv and not pythonEffect:
    818                     env = sharedEnv
    819                 else:
    820                     with bluepy.Timer('LocationManager::Environment'):
    821                         info = dogmaItem.GetEnvironmentInfo()
    822                         env = self.GetEnvironment(info, effectID)
    823                     if sharedEnv is None and not pythonEffect:
    824                         sharedEnv = env
    825                 if env.otherID is None and self.effectCompiler.IsEffectDomainOther(effectID):
    826                     continue
    827                 try:
    828                     self.StartEffect(effectID, itemID, env)
    829                 except UserError as e:
    830                     if not e.msg.startswith('EffectAlreadyActive'):
    831                         raise
    832                     sys.exc_clear()
    833 
    834                 effectsCompleted.add(effectID)
    835 
    836         except Exception:
    837             log.LogException('Failed to start passive effect %s on %s class %s' % (effectID, itemID, dogmaItem.__class__))
    838             return effectsCompleted
    839 
    840     def ShouldStartChanceBasedEffect(self, effectID, itemID, chanceAttributeID):
    841         return False
    842 
    843     def GetActiveEffectEnvironment(self, itemKey, effectID):
    844         dogmaItem = self.dogmaItems[itemKey]
    845         if effectID not in dogmaItem.activeEffects:
    846             return None
    847         return dogmaItem.activeEffects[effectID][const.ACT_IDX_ENV]
    848 
    849     def GetEffect(self, effectID):
    850         return self.dogmaStaticMgr.effects[effectID]
    851 
    852     def GetSubLocation(self, locationID, flag):
    853         if not self.IsItemLoaded(locationID):
    854             raise RuntimeError('GetSubLocation called for unloaded location', locationID, flag)
    855         try:
    856             return self.dogmaItems[locationID].subLocations[flag]
    857         except (KeyError, AttributeError):
    858             return None
    859 
    860     def IsItemSubLocation(self, itemKey):
    861         return type(itemKey) is tuple
    862 
    863     def SetWeaponBanks(self, shipID, data):
    864         self.slaveModulesByMasterModule[shipID] = defaultdict(set)
    865         if data is None:
    866             return
    867         for masterID, slaveIDs in data.iteritems():
    868             for slaveID in slaveIDs:
    869                 self.slaveModulesByMasterModule[shipID][masterID].add(slaveID)
    870 
    871     def GetMasterModuleID(self, shipID, slaveID):
    872         for masterID, slaves in self.slaveModulesByMasterModule.get(shipID, {}).iteritems():
    873             if slaveID in slaves:
    874                 return masterID
    875 
    876     def GetSlaveModules(self, moduleID, shipID):
    877         if shipID in self.slaveModulesByMasterModule and moduleID in self.slaveModulesByMasterModule[shipID]:
    878             return self.slaveModulesByMasterModule[shipID][moduleID].copy()
    879 
    880     def IsModuleMaster(self, moduleID, shipID):
    881         return moduleID in self.slaveModulesByMasterModule.get(shipID, {})
    882 
    883     def IsModuleSlave(self, moduleID, shipID):
    884         return self.GetMasterModuleID(shipID, moduleID) is not None
    885 
    886     def GetAttributesForType(self, typeID):
    887         ret = {}
    888         for attribute in dogma_data.get_type_attributes(typeID):
    889             ret[attribute.attributeID] = attribute.value
    890 
    891         return ret
    892 
    893     def TypeHasAttribute(self, typeID, attributeID):
    894         return self.dogmaStaticMgr.TypeHasAttribute(typeID, attributeID)
    895 
    896     def TypeHasEffect(self, typeID, effectID):
    897         return self.dogmaStaticMgr.TypeHasEffect(typeID, effectID)
    898 
    899     def GetMissingSkills(self, typeID, currentSkills):
    900         ret = {}
    901         for skillID, skillLevel in self.dogmaStaticMgr.GetRequiredSkills(typeID).iteritems():
    902             if skillID not in currentSkills or currentSkills[skillID].effectiveSkillLevel < skillLevel:
    903                 ret[skillID] = skillLevel
    904 
    905         return ret
    906 
    907     @WrappedMethod
    908     def UnloadItem(self, itemKey, item = None):
    909         if itemKey in self.unloadingItems:
    910             self.LogInfo('Item already being unloaded', itemKey)
    911             return
    912         self.unloadingItems.add(itemKey)
    913         try:
    914             self._UnloadItem(itemKey, item)
    915         finally:
    916             self.unloadingItems.remove(itemKey)
    917 
    918     def _UnloadItem_LogOnEntry(self, itemKey, item):
    919         givenInvItemDescription = self.DescribeInvItem(item)
    920         foundDogmaItem = self.dogmaItems.get(itemKey, None)
    921         foundInvItemDescription = self.DescribeInvItem(foundDogmaItem.invItem) if foundDogmaItem else None
    922         totalItems = len(self.dogmaItems)
    923         LogNotice('UnloadItem requested for itemKey: {itemKey}, with given invItem {givenInvItemDescription}, having found dogmaItems entry {foundDogmaItem} with invItem {foundInvItemDescription} (totalItems = {totalItems}) '.format(**locals()))
    924 
    925     def _UnloadItem(self, itemKey, item):
    926         dogmaItem = self.dogmaItems.get(itemKey, None)
    927         if dogmaItem is not None:
    928             dogmaItem.Unload()
    929             self.RemoveItem(itemKey)
    930             self.LogInfo('_UnloadItem::Deleting dogmaItem', itemKey)
    931             del self.dogmaItems[itemKey]
    932 
    933     def RemoveSubLocationFromLocation(self, itemKey):
    934         locationID = itemKey[0]
    935         self.dogmaItems[locationID].RemoveSubLocation(itemKey)
    936 
    937     def GetClassForItem(self, item):
    938         if item.categoryID == categoryShip:
    939             return ShipDogmaItem
    940         elif item.categoryID == categoryStructure:
    941             return StructureDogmaItem
    942         elif item.categoryID == const.categoryStructureModule:
    943             return StructureModuleDogmaItem
    944         elif item.groupID == const.groupCharacter:
    945             return CharacterDogmaItem
    946         elif item.groupID in PROBE_GROUPS:
    947             return ProbeDogmaItem
    948         elif item.categoryID == const.categoryCharge:
    949             return ChargeDogmaItem
    950         elif item.categoryID in (const.categoryModule, const.categorySubSystem):
    951             return ModuleDogmaItem
    952         elif item.categoryID in (const.categoryStructureModule,):
    953             return StructureModuleDogmaItem
    954         elif item.groupID == const.groupControlTower:
    955             return ControlTowerDogmaItem
    956         elif item.categoryID == const.categoryStarbase:
    957             return StarbaseDogmaItem
    958         elif item.categoryID == const.categoryDrone:
    959             return DroneDogmaItem
    960         elif item.categoryID == const.categoryFighter:
    961             return FighterDogmaItem
    962         else:
    963             return BaseDogmaItem
    964 
    965     def GetInstance(self, item):
    966         return self.fakeInstanceRow
    967 
    968     def IntroduceNewItem(self, item):
    969         return self.fakeInstanceRow
    970 
    971     def GetShipItemForFighter(self, itemID):
    972         return None
    973 
    974     def IsFighterOwnerModifiablesAtLocation(self, locationID):
    975         return True
    976 
    977     def IsItemWanted(self, typeID, groupID = None):
    978         return True
    979 
    980     def RemoveDroneFromLocation(self, shipID, droneID):
    981         dogmaItem = self.dogmaItems[shipID]
    982         dogmaItem.UnregisterDrone(droneID)
    983 
    984     def AddDroneToLocation(self, shipID, droneID):
    985         shipItem = self.dogmaItems[shipID]
    986         droneItem = self.dogmaItems[droneID]
    987         shipItem.RegisterDrone(droneItem)
    988 
    989     def IsDroneFitted(self, shipID, droneID):
    990         try:
    991             shipItem = self.dogmaItems[shipID]
    992             return droneID in shipItem.GetDronesInBay()
    993         except KeyError:
    994             return False
    995 
    996     def RegisterCallback(self, attributeID, cb):
    997         if isinstance(attributeID, str):
    998             attributeID = self.dogmaStaticMgr.attributesByName[attributeID].attributeID
    999         if attributeID in self.attributeChangeCallbacksByAttributeID:
   1000             log.LogException('RegisterCallback::Overwriting a callback %s %s' % (attributeID, self.attributeChangeCallbacksByAttributeID[attributeID].__name__))
   1001         self.attributeChangeCallbacksByAttributeID[attributeID] = cb
   1002 
   1003     def UnregisterCallback(self, attributeID):
   1004         if isinstance(attributeID, str):
   1005             attributeID = self.dogmaStaticMgr.attributesByName[attributeID].attributeID
   1006         if attributeID in self.attributeChangeCallbacksByAttributeID:
   1007             del self.attributeChangeCallbacksByAttributeID[attributeID]
   1008 
   1009     def OnCpuAttributeChanged(self, attributeID, itemID, newValue, oldValue = None):
   1010         self.OnFittingChanged(itemID, const.attributeCpuLoad, const.attributeCpuOutput)
   1011 
   1012     def OnPowerAttributeChanged(self, attributeID, itemID, newValue, oldValue = None):
   1013         self.OnFittingChanged(itemID, const.attributePowerLoad, const.attributePowerOutput)
   1014 
   1015     def OnFittingChanged(self, itemID, loadAttributeID, outputAttributeID):
   1016         if isinstance(itemID, tuple):
   1017             return
   1018         if itemID not in self.pilotsByShipID:
   1019             return
   1020         if getattr(self.dogmaItems[itemID], 'isBeingDisembarked', False):
   1021             return
   1022         load = self.GetAttributeValue(itemID, loadAttributeID)
   1023         output = self.GetAttributeValue(itemID, outputAttributeID)
   1024         if output - load < 1e-07:
   1025             self.CheckPowerCpuRequirementForOnlineModules(itemID)
   1026 
   1027     def ScheduleCheckPowerCpuRequirementForOnlineModules(self, shipID):
   1028         if shipID not in self.pendingPowerCpuRequirementChecks:
   1029             self.pendingPowerCpuRequirementChecks.add(shipID)
   1030             uthread.pool('DogmaLocation::CheckPowerCpuRequirementForOnlineModules', self.CheckPowerCpuRequirementForOnlineModules, shipID)
   1031 
   1032     def CheckPowerCpuRequirementForOnlineModules(self, shipID):
   1033         if self.brainUpdate.IsEventHappening(shipID):
   1034             return
   1035         self.pendingPowerCpuRequirementChecks.discard(shipID)
   1036         if self.IsItemLoading(shipID):
   1037             return
   1038         if self.broker is None:
   1039             return
   1040         if not self.IsShipLoaded(shipID):
   1041             return
   1042         if self.IsItemUnloading(shipID):
   1043             return
   1044         dogmaItem = self.dogmaItems[shipID]
   1045         if getattr(dogmaItem, 'isBeingDisembarked', False):
   1046             return
   1047         if dogmaItem.categoryID not in CATEGORY_SHIPS_AND_STRUCTURES:
   1048             return
   1049         powerLoad = self.GetAttributeValue(shipID, const.attributePowerLoad)
   1050         powerOutput = self.GetAttributeValue(shipID, const.attributePowerOutput)
   1051         cpuLoad = self.GetAttributeValue(shipID, const.attributeCpuLoad)
   1052         cpuOutput = self.GetAttributeValue(shipID, const.attributeCpuOutput)
   1053         powerUsage = powerLoad / max(powerOutput, 1e-07)
   1054         cpuUsage = cpuLoad / max(cpuOutput, 1e-07)
   1055         if cpuUsage <= 1.0 and powerUsage <= 1.0:
   1056             return
   1057         resourceAttributeID = const.attributePower if powerUsage > cpuUsage else const.attributeCpu
   1058         self._OfflineModuleUsingMostResource(shipID, dogmaItem, resourceAttributeID)
   1059 
   1060     def _OfflineModuleUsingMostResource(self, shipID, dogmaItem, resourceAttributeID):
   1061         if resourceAttributeID not in (const.attributePower, const.attributeCpu):
   1062             raise ValueError('_OfflineModuleUsingMostResource called with invalid resourceAttributeID')
   1063         if not self.IsShipLoaded(shipID):
   1064             return
   1065         candidates = []
   1066         for moduleID, moduleDogmaItem in dogmaItem.GetFittedItems().iteritems():
   1067             if (moduleID, const.effectOnline) in self.deactivatingEffects:
   1068                 continue
   1069             if not moduleDogmaItem.IsOnline():
   1070                 continue
   1071             resourceUsed = self.GetAttributeValue(moduleID, resourceAttributeID)
   1072             if resourceUsed:
   1073                 if moduleDogmaItem.flagID in serviceSlotFlags:
   1074                     sortKey = (0, resourceUsed)
   1075                 else:
   1076                     sortKey = (1, resourceUsed)
   1077                 candidates.append((sortKey, moduleID))
   1078 
   1079         if len(candidates) == 0:
   1080             self.broker.LogError('The ship', shipID, 'has something modifying resource attribute', resourceAttributeID, "that isn't an online effect")
   1081             return
   1082         itemKey = max(candidates)[1]
   1083         if (itemKey, const.effectOnline) not in self.deactivatingEffects:
   1084             self.OfflineModule(itemKey)
   1085 
   1086     def OnMaxGroupOnlineChanged(self, attributeID, itemID, newValue, oldValue = None):
   1087         pass
   1088 
   1089     def OfflineModule(self, moduleID):
   1090         self.StopEffect(const.effectOnline, moduleID)
   1091 
   1092     def FitItemToSelf(self, item):
   1093         self.OnItemAddedToLocation(item.itemID, item, shipid=item.itemID)
   1094 
   1095     def GetCharacter(self, itemID, flush = False):
   1096         pass
   1097 
   1098     def LoadSublocations(self, itemID):
   1099         for flagID, sublocation in self.instanceFlagQuantityCache.get(itemID, {}).iteritems():
   1100             self.LoadItem((sublocation[0], sublocation[1], sublocation[2]))
   1101 
   1102     def GetAttributesByIndex(self):
   1103         return const.dgmAttributesByIdx
   1104 
   1105     def LoadItemsInLocation(self, itemID):
   1106         pass
   1107 
   1108     def UnloadItemsInLocation(self, locationID):
   1109         pass
   1110 
   1111     def GetItem(self, itemID):
   1112         pass
   1113 
   1114     def OnSublocationAddedToLocation(self, itemKey, shipid = None, pilotID = None, fitting = False, locationName = None, timerName = 'AddUnknown2'):
   1115         pass
   1116 
   1117     def OnItemAddedToLocation(self, locationid, item, shipid = None, pilotID = None, fitting = False, locationName = None, timerName = 'AddUnknown1', byUser = False):
   1118         pass
   1119 
   1120     def GetAttributeValue(self, itemKey, attributeID):
   1121         return self.dogmaItems[itemKey].attributes[attributeID].GetValue()
   1122 
   1123     def GetIndustryCharacterModifiers(self, characterID):
   1124         if not self.IsItemLoaded(characterID):
   1125             raise RuntimeError('Characters must be loaded before you can query about their industry attributes.  Try again later.')
   1126         return {attributeID:self.GetAttributeValue(characterID, attributeID) for attributeID in (m[1] for m in CHARACTER_MODIFIERS)}
   1127 
   1128     def GetAttributeDictForItem(self, itemKey):
   1129         try:
   1130             item = self.dogmaItems[itemKey]
   1131         except KeyError:
   1132             return {}
   1133 
   1134         return {attribID:item.attributes[attribID].GetValue() for attribID in item.attributes}
   1135 
   1136     def ItemHasAttribute(self, itemID, attributeID):
   1137         return itemID in self.dogmaItems and attributeID in self.dogmaItems[itemID].attributes
   1138 
   1139     def SetAttributeValue(self, itemKey, attributeID, value, dirty = True, keepLists = True):
   1140         value, v, dirty = self._SetAttributeValue(itemKey, attributeID, value, dirty, keepLists)
   1141         return value
   1142 
   1143     def _SetAttributeValue(self, itemKey, attributeID, value, dirty, keepLists):
   1144         if itemKey not in self.dogmaItems:
   1145             self.broker.LogInfo('Item', itemKey, 'not loaded')
   1146             return (0, 0, False)
   1147         dogmaAttribute = self.dogmaItems[itemKey].attributes[attributeID]
   1148         oldValue = dogmaAttribute.GetValue()
   1149         if oldValue == value:
   1150             dirty = False
   1151         if value is None:
   1152             logger.error('Tried to set attributesByItemAttribute to None', extra={'oldValue': oldValue})
   1153             return (0, 0, False)
   1154         dogmaAttribute.SetBaseValue(value)
   1155         if itemKey in self.dogmaItems:
   1156             self.OnAttributeChanged(attributeID, itemKey, value, oldValue)
   1157         return (value, oldValue, dirty)
   1158 
   1159     def OnDamageAttributeChange(attributeID, itemKey, value, v):
   1160         pass
   1161 
   1162     @telemetry.ZONE_METHOD
   1163     def OnAttributeChanged(self, attributeID, itemKey, value = None, oldValue = None):
   1164         attrObj = self.dogmaItems[itemKey].attributes[attributeID]
   1165         if value is None:
   1166             value = attrObj.GetValue()
   1167         for _opIdx, dependantAttrObj in attrObj.GetOutgoingModifiers():
   1168             try:
   1169                 if dependantAttrObj.item is None:
   1170                     continue
   1171                 affectedItemID = dependantAttrObj.item.itemID
   1172                 affectedAttributeID = dependantAttrObj.attribID
   1173                 if affectedItemID not in self.unloadingItems and affectedItemID in self.dogmaItems:
   1174                     self.OnAttributeChanged(affectedAttributeID, affectedItemID)
   1175             except ReferenceError:
   1176                 logger.exception('BaseDogmaLocation::OnAttributeChanged: Orphan attribute detected as dependantAttrObj via attrObj', extra={'dependantAttrObj': dependantAttrObj,
   1177                  'attrObj': attrObj})
   1178                 dependantAttrObj.CheckIntegrity()
   1179 
   1180         return value
   1181 
   1182     def GetDamageStateEx(self, itemID, ownerID = None):
   1183         if itemID in self.failedLoadingItems:
   1184             return None
   1185         if itemID not in self.dogmaItems:
   1186             return None
   1187         dogmaItem = self.dogmaItems[itemID]
   1188         if isinstance(dogmaItem, NotWantedDogmaItem):
   1189             return None
   1190         if ownerID and dogmaItem.ownerID != ownerID:
   1191             return None
   1192         ret = []
   1193         v = dogmaItem.attributes[const.attributeShieldCharge].GetFullChargedInfo()
   1194         if v[1] > 0:
   1195             ret.append((v[0] / v[1], v[2], blue.os.GetSimTime()))
   1196         else:
   1197             ret.append(v[0])
   1198         hpAttribute = const.attributeArmorHP
   1199         damageAttribute = const.attributeArmorDamage
   1200         maxDamage = dogmaItem.attributes[hpAttribute].GetValue()
   1201         if maxDamage == 0:
   1202             ret.append(0.0)
   1203         else:
   1204             ret.append(1.0 - dogmaItem.attributes[damageAttribute].GetValue() / maxDamage)
   1205         maxDamage = dogmaItem.attributes[const.attributeHp].GetValue()
   1206         if maxDamage == 0:
   1207             ret.append(0.0)
   1208         else:
   1209             ret.append(1.0 - dogmaItem.attributes[const.attributeDamage].GetValue() / maxDamage)
   1210         return ret
   1211 
   1212     def IsItemLoaded(self, itemID):
   1213         return itemID not in self.loadingItems and itemID in self.dogmaItems
   1214 
   1215     def IsItemLoading(self, itemID):
   1216         return itemID in self.loadingItems
   1217 
   1218     def IsItemUnloading(self, itemKey):
   1219         return itemKey in self.unloadingItems
   1220 
   1221     def IsShipLoaded(self, shipID):
   1222         dogmaItem = self.dogmaItems.get(shipID, None)
   1223         if dogmaItem is None:
   1224             return False
   1225         else:
   1226             return dogmaItem.categoryID in CATEGORY_SHIPS_AND_STRUCTURES
   1227 
   1228     def DescribeInvItem(self, invItem):
   1229         if invItem is None:
   1230             return 'None'
   1231         try:
   1232             invItemCategoryName = evetypes.GetCategoryNameByCategory(invItem.categoryID)
   1233             invItemGroupName = evetypes.GetGroupNameByGroup(invItem.groupID)
   1234             invItemTypeName = evetypes.GetName(invItem.typeID)
   1235             invItemID = invItem.itemID
   1236             return "'{invItemCategoryName}/{invItemGroupName}/{invItemTypeName}' {invItemID}".format(**locals())
   1237         except:
   1238             return '[failed to describe invItem: {}]'.format(invItem)
   1239 
   1240     def _GenericAddModifier(self, operation, toItem, toAttribID, fromAttrib):
   1241         toAttrib = toItem.attributes[toAttribID]
   1242         oldValue = toAttrib.GetValue()
   1243         fromAttrib.AddModifierTo(operation, toAttrib)
   1244         self.OnAttributeChanged(toAttribID, toItem.itemID, oldValue=oldValue)
   1245 
   1246     def _GenericRemoveModifier(self, operation, toItem, toAttribID, fromAttrib, weirdSpecialCase = False):
   1247         toAttrib = toItem.attributes[toAttribID]
   1248         oldValue = toAttrib.GetValue()
   1249         fromAttrib.RemoveModifierTo(operation, toAttrib)
   1250         if weirdSpecialCase:
   1251             toItemID = toItem.itemID
   1252             if not self.IsItemUnloading(toItemID) and oldValue is not None:
   1253                 self.OnAttributeChanged(toAttribID, toItemID, oldValue=oldValue)
   1254         else:
   1255             self.OnAttributeChanged(toAttribID, toItem.itemID, oldValue=oldValue)
   1256 
   1257     def _FindOrDefer(self, itemID, deferFunc, *deferArgs):
   1258         try:
   1259             targetItem = self.dogmaItems[itemID]
   1260             return targetItem
   1261         except KeyError:
   1262             self.modifiersAwaitingTarget[itemID].add((deferFunc,) + deferArgs)
   1263             return None
   1264 
   1265     def _FindOrAbandon(self, itemID, deferFunc, *deferArgs):
   1266         try:
   1267             targetItem = self.dogmaItems[itemID]
   1268             return targetItem
   1269         except KeyError:
   1270             try:
   1271                 self.modifiersAwaitingTarget[itemID].remove((deferFunc,) + deferArgs)
   1272             except KeyError:
   1273                 pass
   1274 
   1275             return None
   1276 
   1277     def _Add(self, theDict, index, toAdd, debugStr = '<none>'):
   1278         try:
   1279             innerContainer = theDict[index]
   1280         except KeyError:
   1281             logger.exception('_Add: no innerContainer found at theDict[index]')
   1282             return
   1283 
   1284         innerContainer.add(toAdd)
   1285 
   1286     def _RemoveAndCleanup(self, theDict, index, toRemove, debugStr = '<none>'):
   1287         try:
   1288             innerContainer = theDict[index]
   1289         except KeyError:
   1290             logger.exception('_RemoveAndCleanup: no innerContainer found at theDict[index]')
   1291             return
   1292 
   1293         innerContainer.discard(toRemove)
   1294         if not theDict[index]:
   1295             del theDict[index]
   1296 
   1297     def _Add_2D(self, theDict, index1, index2, toAdd, debugStr = '<none>'):
   1298         try:
   1299             innerContainer = theDict[index1][index2]
   1300         except KeyError:
   1301             logger.exception('_Add_2D: no innerContainer found at theDict[index1][index2]')
   1302             return
   1303 
   1304         innerContainer.add(toAdd)
   1305 
   1306     def _RemoveAndCleanup_2D(self, theDict, index1, index2, toRemove, debugStr = '<none>'):
   1307         try:
   1308             innerContainer = theDict[index1][index2]
   1309         except KeyError:
   1310             logger.exception('_RemoveAndCleanup_2D: no innerContainer found at theDict[index1][index2]')
   1311             return
   1312 
   1313         innerContainer.discard(toRemove)
   1314         if not theDict[index1][index2]:
   1315             del theDict[index1][index2]
   1316         if not theDict[index1]:
   1317             del theDict[index1]
   1318 
   1319     @telemetry.ZONE_METHOD
   1320     def AddModifier(self, operation, toItemID, toAttribID, fromItemID, fromAttribID):
   1321         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1322         self.AddModifierWithSource(operation, toItemID, toAttribID, fromAttrib)
   1323 
   1324     def AddModifierWithSource(self, operation, toItemID, toAttribID, fromAttrib):
   1325         self.CheckItemsMissingInAddModifier(toItemID, fromAttrib)
   1326         toItem = self._FindOrDefer(toItemID, self.AddModifierWithSource, operation, toItemID, toAttribID, fromAttrib)
   1327         if not toItem:
   1328             return
   1329         self._GenericAddModifier(operation, toItem, toAttribID, fromAttrib)
   1330 
   1331     def CheckItemsMissingInAddModifier(self, toItemID, fromAttrib):
   1332         if not toItemID or not fromAttrib:
   1333             raise RuntimeError('Item missing from AddModifier')
   1334 
   1335     @telemetry.ZONE_METHOD
   1336     def RemoveModifier(self, operation, toItemID, toAttribID, fromItemID, fromAttribID):
   1337         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1338         self.RemoveModifierWithSource(operation, toItemID, toAttribID, fromAttrib)
   1339 
   1340     def RemoveModifierWithSource(self, operation, toItemID, toAttribID, fromAttrib):
   1341         if not toItemID or not fromAttrib:
   1342             return
   1343         toItem = self._FindOrAbandon(toItemID, self.AddModifierWithSource, operation, toItemID, toAttribID, fromAttrib)
   1344         if not toItem:
   1345             return
   1346         self._GenericRemoveModifier(operation, toItem, toAttribID, fromAttrib, weirdSpecialCase=True)
   1347 
   1348     @WrappedMethod
   1349     @telemetry.ZONE_METHOD
   1350     def AddLocationModifier(self, operation, toLocationID, toAttribID, fromItemID, fromAttribID):
   1351         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1352         self.AddLocationModifierWithSource(operation, toLocationID, toAttribID, fromAttrib)
   1353 
   1354     def AddLocationModifierWithSource(self, operation, toLocationID, toAttribID, fromAttrib):
   1355         if not toLocationID:
   1356             raise RuntimeError('Item missing from AddLocationModifier')
   1357         toLocation = self._FindOrDefer(toLocationID, self.AddLocationModifierWithSource, operation, toLocationID, toAttribID, fromAttrib)
   1358         if not toLocation:
   1359             return
   1360         self._Add(toLocation.locationMods, toAttribID, (operation, fromAttrib), debugStr='LocMod for Location {}'.format(toLocation))
   1361         for toItem in filter(lambda i: toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1362             self._GenericAddModifier(operation, toItem, toAttribID, fromAttrib)
   1363 
   1364     @WrappedMethod
   1365     @telemetry.ZONE_METHOD
   1366     def RemoveLocationModifier(self, operation, toLocationID, toAttribID, fromItemID, fromAttribID):
   1367         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1368         self.RemoveLocationModifierWithSource(operation, toLocationID, toAttribID, fromAttrib)
   1369 
   1370     def RemoveLocationModifierWithSource(self, operation, toLocationID, toAttribID, fromAttrib):
   1371         if not toLocationID:
   1372             return
   1373         toLocation = self._FindOrAbandon(toLocationID, self.AddLocationModifierWithSource, operation, toLocationID, toAttribID, fromAttrib)
   1374         if not toLocation:
   1375             return
   1376         self._RemoveAndCleanup(toLocation.locationMods, toAttribID, (operation, fromAttrib), debugStr='LocMod for Location {}'.format(toLocation))
   1377         for toItem in filter(lambda i: toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1378             self._GenericRemoveModifier(operation, toItem, toAttribID, fromAttrib)
   1379 
   1380     @WrappedMethod
   1381     @telemetry.ZONE_METHOD
   1382     def AddLocationGroupModifier(self, operation, toLocationID, groupID, toAttribID, fromItemID, fromAttribID):
   1383         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1384         self.AddLocationGroupModifierWithSource(operation, toLocationID, groupID, toAttribID, fromAttrib)
   1385 
   1386     def AddLocationGroupModifierWithSource(self, operation, toLocationID, groupID, toAttribID, fromAttrib):
   1387         if not toLocationID:
   1388             raise RuntimeError('Item missing from AddLocationGroupModifier')
   1389         toLocation = self._FindOrDefer(toLocationID, self.AddLocationGroupModifierWithSource, operation, toLocationID, groupID, toAttribID, fromAttrib)
   1390         if not toLocation:
   1391             return
   1392         self._Add_2D(toLocation.locationGroupMods, groupID, toAttribID, (operation, fromAttrib), debugStr='LocGrpMod for Location {}'.format(toLocation))
   1393         for toItem in filter(lambda i: i.invItem.groupID == groupID and toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1394             self._GenericAddModifier(operation, toItem, toAttribID, fromAttrib)
   1395 
   1396     @WrappedMethod
   1397     @telemetry.ZONE_METHOD
   1398     def RemoveLocationGroupModifier(self, operation, toLocationID, groupID, toAttribID, fromItemID, fromAttribID):
   1399         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1400         self.RemoveLocationGroupModifierWithSource(operation, toLocationID, groupID, toAttribID, fromAttrib)
   1401 
   1402     def RemoveLocationGroupModifierWithSource(self, operation, toLocationID, groupID, toAttribID, fromAttrib):
   1403         if not toLocationID:
   1404             return
   1405         toLocation = self._FindOrAbandon(toLocationID, self.AddLocationGroupModifierWithSource, operation, toLocationID, groupID, toAttribID, fromAttrib)
   1406         if not toLocation:
   1407             return
   1408         self._RemoveAndCleanup_2D(toLocation.locationGroupMods, groupID, toAttribID, (operation, fromAttrib), debugStr='LocGrpMod for Location {}'.format(toLocation))
   1409         for toItem in filter(lambda i: i.invItem.groupID == groupID and toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1410             self._GenericRemoveModifier(operation, toItem, toAttribID, fromAttrib)
   1411 
   1412     @WrappedMethod
   1413     @telemetry.ZONE_METHOD
   1414     def AddLocationRequiredSkillModifier(self, operation, toLocationID, skillID, toAttribID, fromItemID, fromAttribID):
   1415         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1416         self.AddLocationRequiredSkillModifierWithSource(operation, toLocationID, skillID, toAttribID, fromAttrib)
   1417 
   1418     def AddLocationRequiredSkillModifierWithSource(self, operation, toLocationID, skillID, toAttribID, fromAttrib):
   1419         if not toLocationID:
   1420             raise RuntimeError('Item missing from AddLocationRequiredSkillModifier')
   1421         toLocation = self._FindOrDefer(toLocationID, self.AddLocationRequiredSkillModifierWithSource, operation, toLocationID, skillID, toAttribID, fromAttrib)
   1422         if not toLocation:
   1423             return
   1424         self._Add_2D(toLocation.locationReqSkillMods, skillID, toAttribID, (operation, fromAttrib), debugStr='LocReqSkillMod for Location {}'.format(toLocation))
   1425         for toItem in filter(lambda i: skillID in i.reqSkills and toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1426             self._GenericAddModifier(operation, toItem, toAttribID, fromAttrib)
   1427 
   1428     @WrappedMethod
   1429     @telemetry.ZONE_METHOD
   1430     def RemoveLocationRequiredSkillModifier(self, operation, toLocationID, skillID, toAttribID, fromItemID, fromAttribID):
   1431         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1432         self.RemoveLocationRequiredSkillModifierWithSource(operation, toLocationID, skillID, toAttribID, fromAttrib)
   1433 
   1434     def RemoveLocationRequiredSkillModifierWithSource(self, operation, toLocationID, skillID, toAttribID, fromAttrib):
   1435         if not toLocationID:
   1436             return
   1437         toLocation = self._FindOrAbandon(toLocationID, self.AddLocationRequiredSkillModifierWithSource, operation, toLocationID, skillID, toAttribID, fromAttrib)
   1438         if not toLocation:
   1439             return
   1440         self._RemoveAndCleanup_2D(toLocation.locationReqSkillMods, skillID, toAttribID, (operation, fromAttrib), debugStr='LocReqSkillMod for Location {}'.format(toLocation))
   1441         for toItem in filter(lambda i: skillID in i.reqSkills and toAttribID in i.attributes, self.IterFittedItems(toLocation)):
   1442             self._GenericRemoveModifier(operation, toItem, toAttribID, fromAttrib)
   1443 
   1444     def IterFittedItems(self, toLocation):
   1445         try:
   1446             return toLocation.fittedItems.itervalues()
   1447         except AttributeError:
   1448             return []
   1449 
   1450     @WrappedMethod
   1451     @telemetry.ZONE_METHOD
   1452     def AddOwnerRequiredSkillModifier(self, operation, ownerID, skillID, toAttribID, fromItemID, fromAttribID):
   1453         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1454         self.AddOwnerRequiredSkillModifierWithSource(operation, ownerID, skillID, toAttribID, fromAttrib)
   1455 
   1456     def AddOwnerRequiredSkillModifierWithSource(self, operation, ownerID, skillID, toAttribID, fromAttrib):
   1457         if not ownerID:
   1458             return
   1459         toOwner = self._FindOrDefer(ownerID, self.AddOwnerRequiredSkillModifierWithSource, operation, ownerID, skillID, toAttribID, fromAttrib)
   1460         if not toOwner:
   1461             return
   1462         self._Add_2D(toOwner.ownerReqSkillMods, skillID, toAttribID, (operation, fromAttrib), debugStr='OwnerReqSkillMod for Owner {}'.format(toOwner))
   1463         modItems = filter(lambda i: skillID in i.reqSkills and toAttribID in i.attributes, toOwner.GetOwnedItemsToModify())
   1464         for toItem in modItems:
   1465             self._GenericAddModifier(operation, toItem, toAttribID, fromAttrib)
   1466 
   1467     @WrappedMethod
   1468     @telemetry.ZONE_METHOD
   1469     def RemoveOwnerRequiredSkillModifier(self, operation, ownerID, skillID, toAttribID, fromItemID, fromAttribID):
   1470         fromAttrib = self.dogmaItems[fromItemID].attributes[fromAttribID]
   1471         self.RemoveOwnerRequiredSkillModifierWithSource(operation, ownerID, skillID, toAttribID, fromAttrib)
   1472 
   1473     def RemoveOwnerRequiredSkillModifierWithSource(self, operation, ownerID, skillID, toAttribID, fromAttrib):
   1474         if not ownerID:
   1475             return
   1476         toOwner = self._FindOrAbandon(ownerID, self.AddOwnerRequiredSkillModifierWithSource, operation, ownerID, skillID, toAttribID, fromAttrib)
   1477         if not toOwner:
   1478             return
   1479         self._RemoveAndCleanup_2D(toOwner.ownerReqSkillMods, skillID, toAttribID, (operation, fromAttrib), debugStr='OwnerReqSkillMod for Owner {}'.format(toOwner))
   1480         modItems = filter(lambda i: skillID in i.reqSkills and toAttribID in i.attributes, toOwner.GetOwnedItemsToModify())
   1481         for toItem in modItems:
   1482             self._GenericRemoveModifier(operation, toItem, toAttribID, fromAttrib)
   1483 
   1484     @telemetry.ZONE_METHOD
   1485     def RemoveItem(self, itemID):
   1486         self.dogmaItems[itemID].FlushAllModifiers()
   1487 
   1488     @telemetry.ZONE_METHOD
   1489     def DescribeModifierGraphForDebugWindow(self, itemID, attributeID):
   1490         try:
   1491             itemAttributes = self.dogmaItems[itemID].attributes
   1492             if attributeID in itemAttributes:
   1493                 attribRoot = itemAttributes[attributeID]
   1494             else:
   1495                 return 'AttribID {} not found for ItemID {}'.format(attributeID, itemID)
   1496         except:
   1497             return 'ItemID {} not found in dogmaItems'.format(itemID)
   1498 
   1499         levels = defaultdict(list)
   1500 
   1501         def DescribeAttributeForDebugWindow(attrib):
   1502             attribStr = attrib.friendlyStr()
   1503             base = attrib.GetBaseValue()
   1504             val = attrib.GetValue()
   1505             attribDescription = '{attribStr}: base={base} val={val}'.format(**locals())
   1506             return attribDescription
   1507 
   1508         def DescribeModifierForDebugWindow(operatorName, attrib, relationName = 'from'):
   1509             attribDescription = DescribeAttributeForDebugWindow(attrib)
   1510             modifierDescription = '   {operatorName} {relationName} {attribDescription}'.format(**locals())
   1511             return modifierDescription
   1512 
   1513         def HarvestModifiers(curAttrib, curLevel, isIncoming):
   1514             if isIncoming:
   1515                 direction, relationName, levelDelta = ('Incoming', 'from', -1)
   1516                 modifiers = curAttrib.GetIncomingModifiers()
   1517             else:
   1518                 direction, relationName, levelDelta = 'Outgoing', 'to', +1
   1519                 modifiers = curAttrib.GetOutgoingModifiers()
   1520             if not modifiers:
   1521                 return
   1522             curAttribDescription = DescribeAttributeForDebugWindow(curAttrib)
   1523             levels[curLevel].append('{} Mods for {}:'.format(direction, curAttribDescription))
   1524             for opIdx, attrib in modifiers:
   1525                 opName = OpName(opIdx)
   1526                 levels[curLevel].append(DescribeModifierForDebugWindow(opName, attrib, relationName))
   1527                 HarvestModifiers(attrib, curLevel + levelDelta, isIncoming)
   1528 
   1529         rootInfo = 'Root at address {:#x}'.format(id(attribRoot))
   1530         levels[0].append(rootInfo)
   1531         levels[0].append(DescribeModifierForDebugWindow('Root', attribRoot, 'is'))
   1532         HarvestModifiers(attribRoot, curLevel=-1, isIncoming=True)
   1533         HarvestModifiers(attribRoot, curLevel=1, isIncoming=False)
   1534         levels = sorted([ (level, strings) for level, strings in levels.iteritems() ])
   1535         ret = '\n\n'.join([ 'Level %d:\n\n' % (level,) + '\n'.join(strings) for level, strings in levels ])
   1536         return ret
   1537 
   1538     def EnterCriticalSection(self, k, v):
   1539         if (k, v) not in self.crits:
   1540             self.crits[k, v] = uthread.CriticalSection((k, v))
   1541         self.crits[k, v].acquire()
   1542 
   1543     def LeaveCriticalSection(self, k, v):
   1544         self.crits[k, v].release()
   1545         if (k, v) in self.crits and self.crits[k, v].IsCool():
   1546             del self.crits[k, v]
   1547 
   1548     def FullyLogAttribute(self, itemID, attributeID, reason, force = 0):
   1549         description = self.FullyDescribeAttribute(itemID, attributeID, reason, force)
   1550         self.LogNotice(description)
   1551         return description
   1552 
   1553     def FullyDescribeAttribute(self, itemID, attributeID, reason, force = 0):
   1554         if type(attributeID) is str:
   1555             attribute = self.dogmaStaticMgr.attributesByName[attributeID]
   1556             attributeID = attribute.attributeID
   1557         else:
   1558             attribute = self.dogmaStaticMgr.attributes[attributeID]
   1559         rep = ['Full description requested for attribute %s on item %s, reason: %s' % (attribute.name, self.GetLocName(itemID), reason)]
   1560         stackText = 'Stackable, ' if attribute.stackable else 'Not stackable, '
   1561         stackText += 'higher value is better.' if attribute.highIsGood else 'lower value is better.'
   1562         rep.append(stackText)
   1563         val = self.QueryAttributeValue(itemID, attributeID)
   1564         rep.append('Value:%s' % val)
   1565         if self.IsItemSubLocation(itemID):
   1566             locationID = itemID[0]
   1567             typeID = itemID[2]
   1568             groupID = evetypes.GetGroupID(typeID)
   1569             _locationItem = self.broker.i2.GetItemMx(locationID)
   1570             ownerID = _locationItem.ownerID
   1571         elif itemID in self.dogmaItems:
   1572             _dogmaItem = self.dogmaItems[itemID]
   1573             if _dogmaItem.location is not None:
   1574                 locationID = _dogmaItem.location.itemID
   1575             else:
   1576                 locationID = None
   1577             typeID, groupID, ownerID = _dogmaItem.typeID, _dogmaItem.groupID, _dogmaItem.ownerID
   1578         else:
   1579             item = self.broker.i2.GetItemMx(itemID)
   1580             locationID = item.locationID
   1581             typeID, groupID, ownerID = item.typeID, item.groupID, item.ownerID
   1582         oval = self.dogmaStaticMgr.GetTypeAttribute(typeID, attributeID, 'No idea!')
   1583         rep.append('Original Value:%s' % oval)
   1584         rep.append('Attribute modification graph:')
   1585         rep.append('')
   1586         modifierInfo = self.DescribeModifierGraphForDebugWindow(itemID, attributeID)
   1587         rep.extend(modifierInfo.split('\n'))
   1588         return rep
   1589 
   1590     def QueryAttributeValue(self, itemID, attributeID):
   1591         try:
   1592             val = self.GetAttributeValue(itemID, attributeID)
   1593         except Exception:
   1594             val = '[No idea!]'
   1595             sys.exc_clear()
   1596 
   1597         return val
   1598 
   1599     def QueryAllAttributesForItem(self, itemID):
   1600         return self.GetAttributeDictForItem(itemID)
   1601 
   1602     def GetLocName(self, locid):
   1603         dogmaItem = self.dogmaItems.get(locid, None)
   1604         if dogmaItem is None:
   1605             return 'Unknown Item'
   1606         return evetypes.GetName(dogmaItem.typeID)
   1607 
   1608     def LogInfo(self, *args):
   1609         return self.broker.LogInfo(self.locationID, *args)
   1610 
   1611     def LogNotice(self, *args):
   1612         return self.broker.LogNotice(self.locationID, *args)
   1613 
   1614     def LogWarn(self, *args):
   1615         return self.broker.LogWarn(self.locationID, *args)
   1616 
   1617     def LogError(self, *args):
   1618         return self.broker.LogError(self.locationID, *args)
   1619 
   1620     def IsIgnoringOwnerEvents(self, ownerID):
   1621         return self.ignoredOwnerEvents.IsEventHappening(ownerID)
   1622 
   1623     def ShouldMessageItemEvent(self, itemID, ownerID = None, locationID = None):
   1624         return False
   1625 
   1626     def SlotExists(self, shipID, flagID):
   1627         attributeID = None
   1628         if flagID in const.hiSlotFlags:
   1629             attributeID = const.attributeHiSlots
   1630             slotNumber = flagID - const.flagHiSlot0
   1631         elif flagID in const.medSlotFlags:
   1632             attributeID = const.attributeMedSlots
   1633             slotNumber = flagID - const.flagMedSlot0
   1634         elif flagID in const.loSlotFlags:
   1635             attributeID = const.attributeLowSlots
   1636             slotNumber = flagID - const.flagLoSlot0
   1637         elif flagID in const.rigSlotFlags:
   1638             attributeID = const.attributeRigSlots
   1639             slotNumber = flagID - const.flagRigSlot0
   1640         elif flagID in const.serviceSlotFlags:
   1641             attributeID = const.attributeServiceSlots
   1642             slotNumber = flagID - const.flagServiceSlot0
   1643         if attributeID is not None:
   1644             totalSlots = self.GetAttributeValue(shipID, attributeID)
   1645             if slotNumber >= totalSlots:
   1646                 return False
   1647         return True
   1648 
   1649     def GetSublocations(self, shipID):
   1650         ret = set()
   1651         for subLocation in self.GetDogmaItem(shipID).subLocations.itervalues():
   1652             ret.add(self.GetDogmaItem(subLocation))
   1653 
   1654         return ret
   1655 
   1656     def GetSlotOther(self, shipID, flagID):
   1657         for item in self.dogmaItems[shipID].GetFittedItems().itervalues():
   1658             if item.flagID == flagID and IsFittingModule(item.categoryID):
   1659                 return item.itemID
   1660 
   1661     @telemetry.ZONE_METHOD
   1662     def CheckSkillRequirements(self, charID, skillID, errorMsgName):
   1663         skillItem = self.dogmaItems[skillID]
   1664         self.CheckSkillRequirementsForType(skillItem.typeID, errorMsgName)
   1665 
   1666     def CheckSkillRequirementsForType(self, typeID, errorMsgName):
   1667         pass
   1668 
   1669     def ProcessBrainData(self, charID, brainData):
   1670         brainVersion, grayMatter = brainData
   1671         if grayMatter:
   1672             charEffects, shipEffects, structureEffects = blue.marshal.Load(grayMatter)
   1673             self.broker.LogInfo('Got a brain with', len(charEffects), 'char effects, ', len(shipEffects), 'ship effects and', len(structureEffects), 'structure effects')
   1674             brain = (brainVersion,
   1675              charEffects,
   1676              shipEffects,
   1677              structureEffects)
   1678             self.SetBrainData(charID, brain)
   1679 
   1680     def CanFitItemInSpace(self, charID, shipID, itemID):
   1681         return True
   1682 
   1683     def CheckSlotOnliningAndExistence(self, shipID, moduleID):
   1684         pass
   1685 
   1686     def PersistItem2(self, itemID):
   1687         pass
   1688 
   1689     def PersistItem(self, itemID):
   1690         pass
   1691 
   1692     def GetEnvironment(self, info, effectID, targetID = None):
   1693         return Environment(itemID=info.itemID, charID=info.charID, shipID=info.shipID, targetID=targetID, otherID=info.otherID, effectID=effectID, dogmaLM=weakref.proxy(self), expressionID=None, structureID=info.structureID)
   1694 
   1695     def _UpdateShipEffectsFromBrain(self, updateFunction, updateFunctionType, shipEffects, structureEffects, shipId, shipDogmaItem):
   1696         effects = self._PickShipOrStructureEffects(shipEffects, structureEffects, shipDogmaItem.categoryID)
   1697         self._LogUpdatedBrainEffects(updateFunctionType, len(effects), 'ship/structure', shipId)
   1698         validAttributes = self._GetValidAttributes(shipDogmaItem.typeID)
   1699         for effect in effects:
   1700             if not self._IsShipEffectValid(effect, validAttributes):
   1701                 continue
   1702             literal = self.brainLiterals[effect.GetLiteralKey()]
   1703             effectType = effect.modifierType
   1704             updateFunction[effectType](self, shipId, literal, *effect.GetApplicationArgs())
   1705 
   1706     def _PickShipOrStructureEffects(self, shipEffects, structureEffects, categoryId):
   1707         if categoryId == categoryShip:
   1708             return shipEffects
   1709         return structureEffects
   1710 
   1711     def _GetValidAttributes(self, shipTypeId):
   1712         validAttributes = {x.attributeID for x in dogma_data.get_type_attributes(shipTypeId)}
   1713         validAttributes.add(const.attributeCapacity)
   1714         return validAttributes
   1715 
   1716     def _IsShipEffectValid(self, effect, validAttributes):
   1717         return effect.modifierType != 'M' or effect.toAttribID in validAttributes
   1718 
   1719     def _UpdateCharacterEffectsFromBrain(self, updateFunction, updateFunctionType, characterEffects, characterId):
   1720         self._LogUpdatedBrainEffects(updateFunctionType, len(characterEffects), 'character', characterId)
   1721         for effect in characterEffects:
   1722             literal = self.brainLiterals[effect.GetLiteralKey()]
   1723             effType = effect.modifierType
   1724             updateFunction[effType](self, characterId, literal, *effect.GetApplicationArgs())
   1725 
   1726     def _LogUpdatedBrainEffects(self, updateFunctionType, effectAmount, domainType, domainId):
   1727         self.broker.LogInfo('%s - %s %s %s effects to %s' % (self.locationID,
   1728          updateFunctionType,
   1729          effectAmount,
   1730          domainType,
   1731          domainId))
   1732 
   1733     def GetStructureIdForEnvironment(self, shipId):
   1734         if shipId is not None and self.IsItemIdStructure(shipId):
   1735             return shipId
   1736 
   1737     def IsItemIdStructure(self, itemId):
   1738         shipDogmaItem = self.dogmaItems.get(itemId, None)
   1739         return shipDogmaItem and shipDogmaItem.categoryID == categoryStructure
   1740 
   1741     def UpdateStructureServices(self, structureID):
   1742         pass
   1743 
   1744     def UninstallPilotFromShipAndItems(self, oldShipID):
   1745         dogmaItem = self.dogmaItems.get(oldShipID)
   1746         if dogmaItem is None:
   1747             return
   1748         fittedItems = dogmaItem.GetFittedItems()
   1749         for itemKey in fittedItems:
   1750             self.UninstallPilotForItem(itemKey)
   1751 
   1752     def UninstallPilotForItem(self, itemKey):
   1753         dogmaItem = self.dogmaItems[itemKey]
   1754         for effectID in dogmaItem.activeEffects.keys():
   1755             effect = self.dogmaStaticMgr.effects[effectID]
   1756             if effect.effectCategory == const.dgmEffPassive and self.effectCompiler.IsEffectCharacterModifier(effectID):
   1757                 self.LogInfo('UninstallPilotForItem.effect', itemKey, effectID, dogma_data.get_effect_name(effectID))
   1758                 self.StopEffect(effectID, itemKey, False)
   1759 
   1760         if dogmaItem.categoryID == const.categoryDrone and dogmaItem.IsOwnerModifiable():
   1761             dogmaItem.RemoveDroneOwnerModifiers()
   1762 
   1763     def GetCharacterSecurityStatus(self, charID):
   1764         return 0.0
   1765 
   1766     def _ApplyPilotSecurityStatusToShip(self, shipID, pilotID):
   1767         if pilotID:
   1768             secStatus = self.GetCharacterSecurityStatus(pilotID)
   1769         else:
   1770             secStatus = 0.0
   1771         self.SetAttributeValue(shipID, const.attributePilotSecurityStatus, secStatus)
   1772 
   1773     def FindNumUnitsToRepairModule(self, _charid, dmg, hp, itemTypeID, itemID):
   1774         consumptionTypeID = const.consumptionTypeIDForModuleRepair
   1775         basePrice = GetComputedBasePrice(itemTypeID, 1)
   1776         if basePrice == 0.0:
   1777             self.SetAttributeValue(itemID, const.attributeDamage, 0)
   1778             return None
   1779         unitPrice = basePrice / hp
   1780         costToRepair = dmg * unitPrice * self.GetAttributeValue(_charid, const.attributeShipBrokenModuleRepairCostMultiplier)
   1781         unitsConsumed = math.ceil(costToRepair / evetypes.GetBasePrice(consumptionTypeID))
   1782         return unitsConsumed
   1783 
   1784     def GetAttributesForItem(self, itemID, typeID):
   1785         if itemID and dynamicitemattributes.IsDynamicType(typeID):
   1786             return sm.GetService('dynamicItemSvc').GetDynamicItemAttributes(itemID)
   1787         ret = {}
   1788         for attribute in dogma_data.get_type_attributes(typeID):
   1789             ret[attribute.attributeID] = attribute.value
   1790 
   1791         return ret
   1792 
   1793     def IsAttributeInIcomingModifiers(self, itemID, toAttributeID, attributeToCheck, operation):
   1794         dogmaItem = self.GetItem(itemID)
   1795         if not dogmaItem:
   1796             return False
   1797         toAttrib = dogmaItem.attributes.get(toAttributeID, None)
   1798         if not toAttrib:
   1799             return False
   1800         attributesForOp = toAttrib.incomingModifiers[dgmOperatorIDtoIndex[operation]]
   1801         return attributeToCheck in attributesForOp