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

itemPickerMenu.py (21446B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\fittingScreen\itemPickerMenu.py
      2 import carbonui.const as uiconst
      3 from collections import defaultdict
      4 import evetypes
      5 import uthread2
      6 import dynamicitemattributes
      7 from carbonui.primitives.container import Container
      8 from carbonui.primitives.containerAutoSize import ContainerAutoSize
      9 from carbonui.primitives.sprite import Sprite
     10 from carbonui.util.sortUtil import SortListOfTuples
     11 from carbonui.control.buttonIcon import ButtonIcon
     12 from carbonui.control.button import Button
     13 from eve.client.script.ui.control.entries.generic import Generic
     14 from eve.client.script.ui.control.entries.mouse_inside_scroll_entry import MouseInsideScrollEntry
     15 from eve.client.script.ui.control.entries.util import GetFromClass
     16 from eve.client.script.ui.control.eveIcon import Icon
     17 from eve.client.script.ui.control.eveLabel import EveLabelMedium, EveLabelLarge
     18 from eve.client.script.ui.control.eveScroll import Scroll
     19 from eve.client.script.ui.control.labelEditable import LabelEditable
     20 from eve.client.script.ui.shared.fitting.fittingUtil import EatSignalChangingErrors
     21 from eve.client.script.ui.shared.fittingScreen.skillRequirements import GetMissingSkills_HighestLevelByTypeID
     22 from eve.client.script.ui.util.uix import QtyPopup, GetTechLevelIcon
     23 from localization import GetByLabel
     24 import inventorycommon.const as invConst
     25 from shipfitting.fittingWarningConst import WARNING_LEVEL_SKILL
     26 from shipfitting.fittingWarnings import GetColorForLevel
     27 from signals.signalUtil import ChangeSignalConnect
     28 from utillib import KeyVal
     29 from carbonui.uicore import uicore
     30 from eveservices.menu import GetMenuService
     31 from eve.common.lib import appConst as const
     32 
     33 class ItemPickerBase(MouseInsideScrollEntry):
     34     isDragObject = True
     35     ENTRYHEIGHT = 32
     36     ICONWIDTH = ENTRYHEIGHT - 4
     37     NUM_CONT_WIDTH = 80
     38     REMOVE_PADDING = 36
     39     haveSkillsLabelPath = 'UI/Fitting/FittingWindow/HaveSkillsForItem'
     40     dontHaveSkillsLabelPath = 'UI/Fitting/FittingWindow/MissingSkillsForItem'
     41 
     42     def ApplyAttributes(self, attributes):
     43         MouseInsideScrollEntry.ApplyAttributes(self, attributes)
     44         self.fittingWarning = None
     45         node = attributes.node
     46         self.sr.node = node
     47         self.updateNumThread = None
     48         self.InitializeFuncs(node)
     49         leftContWidth = self.NUM_CONT_WIDTH + self.ENTRYHEIGHT
     50         leftCont = Container(name='leftCont', parent=self, align=uiconst.TOLEFT, width=leftContWidth)
     51         self.numItemsCont = Container(name='numItemsCont', parent=leftCont, align=uiconst.TOLEFT, width=self.NUM_CONT_WIDTH, state=uiconst.UI_NORMAL)
     52         self.restCont = Container(name='restCont', parent=self, align=uiconst.TOALL, left=10, width=self.REMOVE_PADDING)
     53         self.iconCont = Container(name='iconCont', parent=leftCont, align=uiconst.TORIGHT, width=self.ENTRYHEIGHT)
     54         self.typeIcon = Icon(parent=self.iconCont, pos=(0,
     55          0,
     56          self.ICONWIDTH,
     57          self.ICONWIDTH), align=uiconst.CENTERRIGHT, idx=0, state=uiconst.UI_DISABLED)
     58         self.techIcon = Sprite(name='techIcon', parent=self.iconCont, left=1, width=16, height=16, idx=0)
     59         self.label = EveLabelMedium(text='', parent=self.restCont, left=0, top=0, state=uiconst.UI_DISABLED, color=None, maxLines=1, align=uiconst.CENTER)
     60         self.AddRemoveBtn()
     61         self.numItems = LabelEditable(name='numDrones_LabelEditable', parent=self.numItemsCont, align=uiconst.CENTERRIGHT, pos=(0, 1, 0, 0), text='10', hint=GetByLabel('UI/Fitting/FittingWindow/ClickToEdit'), configName='numDrones', maxLength=6, minLength=1, minValue=0, maxValue=1000000, defaultText='0', fontsize=18, onEditFieldChanged=self.OnEditFieldChanged, textEditFieldSwitchCallback=self.OnTextEditFieldChanges)
     62         self.numItemsCont.OnClick = self.numItems.OnLabelClicked
     63         self.numItemsCont.GetMenu = self.GetMenu
     64         self.numItemsCont.isDragObject = True
     65         self.numItemsCont.GetDragData = self.GetDragData
     66         self.numItemsCont.hint = GetByLabel('UI/Fitting/FittingWindow/ClickToEdit')
     67         self.xLabel = EveLabelLarge(text='x', parent=self.numItemsCont, left=self.numItems.left + 9, top=1, state=uiconst.UI_DISABLED, color=None, maxLines=1, align=uiconst.CENTERRIGHT)
     68 
     69     def ConstructFittingWarning(self):
     70         if self.fittingWarning and not self.fittingWarning.destroyed:
     71             return
     72         self.fittingWarning = Sprite(parent=self.iconCont, name='fittingWarning', state=uiconst.UI_DISABLED, align=uiconst.BOTTOMLEFT, pos=(4, 4, 16, 16), texturePath='res:/UI/Texture/classes/Fitting/slotWarningIcon.png', idx=0)
     73 
     74     def Load(self, node):
     75         self.typeID = node.typeID
     76         self.label.text = node.label
     77         self.typeIcon.SetSize(self.ENTRYHEIGHT - 4, self.ENTRYHEIGHT - 4)
     78         self.typeIcon.LoadIconByTypeID(typeID=self.typeID, size=self.ICONWIDTH, ignoreSize=True, isCopy=getattr(node, 'isCopy', False))
     79         techSprite = GetTechLevelIcon(self.techIcon, 1, self.typeID)
     80         techSprite.left = 4
     81         self.numItems.SetValue(node.qty)
     82         self.SetEditFieldRange()
     83 
     84     def InitializeFuncs(self, node):
     85         self.addToActiveFunc = node.addToActive
     86         self.removeFromActiveFunc = node.removeFromActive
     87         self.changeActive = node.changeActive
     88         self.changeNumFunc = node.changeNum
     89 
     90     def OnTextEditFieldChanges(self, *args):
     91         if self.numItems.editField.display:
     92             self.xLabel.display = False
     93         else:
     94             self.xLabel.display = True
     95 
     96     def OnEditFieldChanged(self, *args):
     97         LabelEditable.OnEditFieldChanged(self.numItems, *args)
     98         if not self.updateNumThread:
     99             self.updateNumThread = uthread2.call_after_wallclocktime_delay(self.UpdateNumbers_thread, 0.25)
    100 
    101     def UpdateNumbers_thread(self):
    102         self.updateNumThread = None
    103         numWanted = self.numItems.GetValue()
    104         if self.changeNumFunc:
    105             self.changeNumFunc(self.sr.node.typeID, numWanted)
    106 
    107     def SetEditFieldRange(self):
    108         node = self.sr.node
    109         self.numItems.editField.SetMinValue(node.intRange[0])
    110         self.numItems.editField.SetMaxValue(node.intRange[1])
    111 
    112     def AddRemoveBtn(self):
    113         self.removeBtn = ButtonIcon(texturePath='res:/UI/Texture/Icons/73_16_210.png', pos=(2, 0, 16, 16), align=uiconst.CENTERRIGHT, parent=self, hint=GetByLabel('UI/Generic/RemoveItem'), idx=0, func=self.RemoveItem)
    114         self.haveSkillIcon = Sprite(name='haveIcon', parent=self, pos=(20, 0, 16, 16), align=uiconst.CENTERRIGHT, state=uiconst.UI_NORMAL)
    115         self.haveSkillIcon.display = False
    116 
    117     def RemoveItem(self):
    118         node = self.sr.node
    119         if node.Get('removeFunc', None):
    120             node.removeFunc(node)
    121 
    122     def GetDragData(self):
    123         typeID = self.sr.node.typeID
    124         keyVal = KeyVal(__guid__='listentry.GenericMarketItem', typeID=typeID, label=evetypes.GetName(typeID), flagID=self.sr.node.flagID)
    125         return [keyVal]
    126 
    127     def GetHeight(self, *args):
    128         node, width = args
    129         node.height = ItemPickerBase.ENTRYHEIGHT
    130         return node.height
    131 
    132     @classmethod
    133     def GetEntryWidth(cls, node):
    134         label = node['label']
    135         textwidth, _ = EveLabelMedium.MeasureTextSize(label, maxLines=1)
    136         w = cls.NUM_CONT_WIDTH + cls.ENTRYHEIGHT + textwidth + cls.REMOVE_PADDING + 40
    137         return w
    138 
    139     def GetMenu(self):
    140         if self.sr.node.itemIDs:
    141             itemID = list(self.sr.node.itemIDs)[0]
    142         else:
    143             itemID = None
    144         return GetMenuService().GetMenuFromItemIDTypeID(itemID, self.sr.node.typeID, includeMarketDetails=True)
    145 
    146     def SetFittingWarningColor(self, color):
    147         if color is None:
    148             if self.fittingWarning:
    149                 self.fittingWarning.display = False
    150         else:
    151             self.ConstructFittingWarning()
    152             self.fittingWarning.display = True
    153             self.fittingWarning.SetRGBA(*color)
    154 
    155 
    156 class ItemPickerMenu(object):
    157     entryClass = ItemPickerBase
    158     emptyLabelPath = 'UI/Fitting/FittingWindow/NoCargoItemsSimulated'
    159 
    160     def __init__(self, controller, menuParent):
    161         self.scrollWidth = 100
    162         self.fittingDogmaLocation = sm.GetService('clientDogmaIM').GetFittingDogmaLocation()
    163         self.ghostFittingSvc = sm.GetService('ghostFittingSvc')
    164         self.scroll = Scroll(parent=menuParent, align=uiconst.TOTOP, height=100, padding=4)
    165         self.btnCont = ContainerAutoSize(parent=menuParent, align=uiconst.TOTOP, padTop=4, padBottom=4)
    166         removeBtn = Button(parent=self.btnCont, label=GetByLabel('UI/Fitting/FittingWindow/RemoveAll'), func=self.RemoveAll, align=uiconst.CENTER)
    167         self.scroll.GetEntryWidth = self.GetEntryWidth
    168 
    169     def LoadItemScroll(self):
    170         self.scroll.Clear()
    171         sortedItemInfo = self.GetSortedItemsInfo()
    172         activeItemsSet = set(self.GetActiveItems())
    173         extraData = self.GetExtraData()
    174         extraDataFunc = self.GetExtraDataFuncs()
    175         maxWidth = 100
    176         scrollList = []
    177         for itemInfo in sortedItemInfo:
    178             hasSkills = sm.GetService('skills').IsSkillRequirementMet(itemInfo.typeID)
    179             qty = itemInfo.qty
    180             maxExtraQty = GetMaxQty(self.fittingDogmaLocation, self.flagID, {itemInfo.typeID: 1}) or 0
    181             intRange = (0, qty + maxExtraQty)
    182             label = itemInfo.typeName
    183             data = {'label': label,
    184              'itemIDs': itemInfo.itemIDs,
    185              'intRange': intRange,
    186              'typeID': itemInfo.typeID,
    187              'isDynamic': itemInfo.isDynamic,
    188              'getIcon': True,
    189              'OnClick': self.OnItemClicked,
    190              'removeFunc': self.RemoveItemClicked,
    191              'hasSkills': hasSkills,
    192              'qty': qty,
    193              'flagID': self.flagID}
    194             data.update(extraData)
    195             data.update(extraDataFunc)
    196             extraDataForType = self.GetExtraDataForType(itemInfo.itemIDs, activeItemsSet, itemInfo.typeID)
    197             data.update(extraDataForType)
    198             width = self.entryClass.GetEntryWidth(data)
    199             maxWidth = max(maxWidth, width)
    200             entry = GetFromClass(self.entryClass, data)
    201             scrollList.append(entry)
    202 
    203         if scrollList:
    204             entry = self.GetHeaderEntry(activeItemsSet)
    205             if entry:
    206                 scrollList.insert(0, entry)
    207             self.scrollWidth = maxWidth
    208             self.btnCont.display = True
    209         else:
    210             scrollList = self.GetNoItemEntryAndSetWidth()
    211             if not self.DoShowBtnWhenScollIsEmpty():
    212                 self.btnCont.display = False
    213         self.scroll.AddNodes(0, scrollList)
    214         self.UpdateScrollSize()
    215 
    216     def RefrehsScroll(self):
    217         self.scroll.RefreshNodes()
    218 
    219     def DoShowBtnWhenScollIsEmpty(self):
    220         return False
    221 
    222     def UpdateNodes(self):
    223         pass
    224 
    225     def IsItemChecked(self, itemID, activeItemsCopy):
    226         return True
    227 
    228     def GetActiveItems(self):
    229         return {}
    230 
    231     def GetItems(self):
    232         return {}
    233 
    234     def GetTotalCanActivate(self, numActive):
    235         return 0
    236 
    237     def OnItemClicked(self, *args):
    238         pass
    239 
    240     def UpdateScrollSize(self):
    241         contentHeight = self.scroll.GetContentHeight() + 2
    242         self.scroll.height = min(contentHeight, 400)
    243 
    244     def GetEntryWidth(self, *args):
    245         return self.scrollWidth
    246 
    247     def RemoveItemClicked(self, node):
    248         self.RemoveItem(node)
    249         self.scroll.RemoveNodes([node])
    250         if not self.scroll.GetNodes():
    251             scrollList = self.GetNoItemEntryAndSetWidth()
    252             if not self.DoShowBtnWhenScollIsEmpty():
    253                 self.btnCont.display = False
    254             self.scroll.AddNodes(0, scrollList)
    255         self.UpdateScrollSize()
    256 
    257     def RemoveItem(self, node):
    258         itemIDs = node.itemIDs
    259         if not itemIDs:
    260             return
    261         itemID = list(itemIDs)[0]
    262         self.ghostFittingSvc.UnfitModule(itemID)
    263 
    264     def GetNoItemEntryAndSetWidth(self):
    265         data = {'label': GetByLabel(self.emptyLabelPath)}
    266         data.update(self.GetNoItemDropFunc())
    267         scrollList = [GetFromClass(NoItemEntry, data)]
    268         width = NoItemEntry.GetEntryWidth(NoItemEntry, data)
    269         self.scrollWidth = width
    270         return scrollList
    271 
    272     def GetNoItemDropFunc(self):
    273         return {}
    274 
    275     def GetSortedItemsInfo(self):
    276         itemsToSort = self.GetItems()
    277         unsortedEntries = {}
    278         dynamicItemSvc = sm.GetService('dynamicItemSvc')
    279         for eachItem in itemsToSort.itervalues():
    280             identifier = None
    281             if dynamicItemSvc.IsDynamicItem(eachItem.typeID):
    282                 identifier = eachItem.itemID
    283             itemKey = (eachItem.typeID, identifier)
    284             itemEntryData = unsortedEntries.get(itemKey)
    285             if not itemEntryData:
    286                 itemEntryData = ItemPickerItemEntryData(typeID=eachItem.typeID, typeName=evetypes.GetName(eachItem.typeID), isDynamic=identifier is not None, itemIDs=set(), qty=0)
    287                 unsortedEntries[itemKey] = itemEntryData
    288             itemEntryData.itemIDs.add(eachItem.itemID)
    289             itemEntryData.qty += getattr(eachItem.invItem, 'quantity', None) or getattr(eachItem, 'stacksize', 1)
    290 
    291         return sorted(unsortedEntries.values(), key=lambda data: data.typeName)
    292 
    293     def RemoveAll(self, *args):
    294         for eachNode in self.scroll.GetNodes():
    295             self.RemoveItem(eachNode)
    296 
    297         self.ghostFittingSvc.SendOnStatsUpdatedEvent()
    298         self.LoadItemScroll()
    299 
    300     def GetExtraData(self):
    301         return {}
    302 
    303     def GetExtraDataFuncs(self):
    304         return {}
    305 
    306     def GetExtraDataForType(self, itemIDs, itemIdSet, typeID):
    307         return {}
    308 
    309     def GetHeaderEntry(self, extraInfo):
    310         return None
    311 
    312     def GetAllTypeIDsInScroll(self):
    313         allTypeIDsInScroll = filter(None, {n.typeID for n in self.scroll.GetNodes()})
    314         return allTypeIDsInScroll
    315 
    316     def HiliteProblematicEntries(self, warningSlotDict):
    317         pass
    318 
    319     def UpdateProblematicEntries(self, warningLevel):
    320         typeIDsToUpdate = self.GetAllTypeIDsInScroll()
    321         if warningLevel and warningLevel == WARNING_LEVEL_SKILL:
    322             _, typeIDsToUpdate = GetMissingSkills_HighestLevelByTypeID(typeIDsToUpdate)
    323         color = GetColorForLevel(warningLevel)
    324         for eachNode in self.scroll.GetNodes():
    325             if not eachNode.typeID:
    326                 continue
    327             elif eachNode.typeID not in typeIDsToUpdate:
    328                 eachNode.warningLevel = None
    329                 continue
    330             eachNode.warningLevel = warningLevel
    331             if eachNode.panel and getattr(eachNode.panel, 'SetFittingWarningColor', None):
    332                 eachNode.panel.SetFittingWarningColor(color)
    333 
    334 
    335 class ItemPickerItemEntryData(object):
    336 
    337     def __init__(self, typeID, itemIDs, isDynamic, typeName, qty):
    338         self.typeID = typeID
    339         self.itemIDs = itemIDs
    340         self.isDynamic = isDynamic
    341         self.typeName = typeName
    342         self.qty = qty
    343 
    344 
    345 class BaseHoldItemPickerMenu(ItemPickerMenu):
    346     entryClass = ItemPickerBase
    347     flagID = const.flagCargo
    348 
    349     def GetItems(self):
    350         items = self.fittingDogmaLocation.GetHoldItems(self.flagID)
    351         return items
    352 
    353     def RemoveAll(self, *args):
    354         self.fittingDogmaLocation.RemoveAllItemsFromHold(self.flagID)
    355         self.ghostFittingSvc.SendOnStatsUpdatedEvent()
    356         self.LoadItemScroll()
    357 
    358     def GetExtraDataFuncs(self):
    359         return {'changeNum': self.ChangeNumItems}
    360 
    361     def ChangeNumItems(self, typeID, numWanted):
    362         self.ghostFittingSvc.ModifyHoldItemStackSize(typeID, numWanted, self.flagID)
    363         self.UpdateNodes()
    364 
    365     def HiliteProblematicEntries(self, warningSlotDict):
    366         warningLevel = warningSlotDict.get(self.flagID, None)
    367         self.UpdateProblematicEntries(warningLevel)
    368 
    369     def UpdateNodes(self):
    370         qtyByTypeID = {itemInfo.typeID:itemInfo.qty for itemInfo in self.GetSortedItemsInfo()}
    371         for eachNode in self.scroll.GetNodes():
    372             typeID = eachNode.typeID
    373             if not typeID:
    374                 continue
    375             qty = qtyByTypeID.get(typeID, 0)
    376             maxExtraQty = GetMaxQty(self.fittingDogmaLocation, eachNode.flagID, {typeID: 1}) or 0
    377             intRange = (0, qty + maxExtraQty)
    378             eachNode.intRange = intRange
    379 
    380         for eachNode in self.scroll.GetNodes():
    381             if eachNode.panel:
    382                 eachNode.panel.SetEditFieldRange()
    383 
    384 
    385 class CargoItemPickerMenu(BaseHoldItemPickerMenu):
    386     entryClass = ItemPickerBase
    387     flagID = const.flagCargo
    388 
    389     def __init__(self, controller, menuParent):
    390         BaseHoldItemPickerMenu.__init__(self, controller, menuParent)
    391         self.controller = controller
    392         self.ChangeSignalConnection(connect=True)
    393 
    394     def ChangeSignalConnection(self, connect = True):
    395         signalAndCallback = [(self.controller.on_warning_display_changed, self.HiliteProblematicEntries)]
    396         ChangeSignalConnect(signalAndCallback, connect)
    397 
    398     def DisconnectController(self):
    399         with EatSignalChangingErrors(errorMsg='CargoItemPickerMenu'):
    400             self.ChangeSignalConnection(connect=False)
    401         self.controller = False
    402 
    403 
    404 def GetModifiedQtyDict(fittingDogmaLocation, qtyByTypeIDs, flagID):
    405     if uicore.uilib.Key(uiconst.VK_SHIFT):
    406         qty = FindQty(fittingDogmaLocation, qtyByTypeIDs, flagID)
    407         if qty is None:
    408             return {}
    409         for eachTypeID in qtyByTypeIDs:
    410             qtyByTypeIDs[eachTypeID] = qty
    411 
    412     return qtyByTypeIDs
    413 
    414 
    415 def FindQty(fittingDogmaLocation, qtyByTypeIDs, flagID):
    416     maxQty = GetMaxQty(fittingDogmaLocation, flagID, qtyByTypeIDs)
    417     if not maxQty:
    418         return
    419     ret = QtyPopup(maxvalue=maxQty, minvalue=1)
    420     if ret is None:
    421         qty = None
    422     else:
    423         qty = ret['qty']
    424     return qty
    425 
    426 
    427 def GetMaxQty(fittingDogmaLocation, flagID, qtyByTypeIDs):
    428     shipID = fittingDogmaLocation.GetCurrentShipID()
    429     capacityInfo = fittingDogmaLocation.GetCapacity(shipID, None, flagID)
    430     volumeLeft = capacityInfo.capacity - capacityInfo.used
    431     maxQtyForType = 0
    432     allTypeVol = 0
    433     for typeID in qtyByTypeIDs:
    434         if isinstance(typeID, tuple):
    435             typeID = typeID[0]
    436         typeVolume = float(evetypes.GetVolume(typeID))
    437         allTypeVol += typeVolume
    438         maxQtyForType = max(maxQtyForType, int(volumeLeft / typeVolume))
    439 
    440     maxQty = 0
    441     if allTypeVol:
    442         maxQty = volumeLeft / allTypeVol
    443     if not maxQtyForType:
    444         return
    445     return int(maxQty)
    446 
    447 
    448 def GetDronesInDamgeAmountOrder(fittingDogmaLocation, droneIDs):
    449     damageTypeAttibutes = [const.attributeEmDamage,
    450      const.attributeThermalDamage,
    451      const.attributeKineticDamage,
    452      const.attributeExplosiveDamage]
    453     topDamgeOrder = []
    454     GAV = fittingDogmaLocation.GetAttributeValue
    455     for eachDroneID in droneIDs:
    456         damageModifier = GAV(eachDroneID, const.attributeDamageMultiplier)
    457         dmg = sum({GAV(eachDroneID, x) for x in damageTypeAttibutes})
    458         totalDmg = damageModifier * dmg
    459         topDamgeOrder.append((totalDmg, eachDroneID))
    460 
    461     topDamgeOrder = SortListOfTuples(topDamgeOrder, reverse=True)
    462     return topDamgeOrder
    463 
    464 
    465 class NoItemEntry(Generic):
    466     __guid__ = 'NoItemEntry'
    467     default_showHilite = False
    468     ENTRYHEIGHT = 28
    469 
    470     def GetHeight(self, *args):
    471         return NoItemEntry.ENTRYHEIGHT
    472 
    473     def GetEntryWidth(cls, node):
    474         label = node['label']
    475         textwidth, textheight = EveLabelMedium.MeasureTextSize(label, maxLines=1)
    476         return textwidth + 20
    477 
    478     def OnDropData(self, dragObj, nodes):
    479         onDroppedInListFunc = getattr(self.sr.node, 'onDroppedInListFunc', None)
    480         if onDroppedInListFunc:
    481             onDroppedInListFunc(nodes)
    482 
    483 
    484 VALID_CHARGE_CATEGORIES = [invConst.categoryCharge, invConst.categoryImplant]
    485 VALID_CHARGE_GROUPS = [invConst.groupIceProduct]
    486 VALID_MODULE_CATEGORIES = [invConst.categoryModule, invConst.categorySubSystem]
    487 
    488 def IsValidForHold(typeID, flagID):
    489     categoryID = evetypes.GetCategoryID(typeID)
    490     if flagID == const.flagCargo:
    491         if categoryID in VALID_CHARGE_CATEGORIES:
    492             return True
    493         if categoryID in VALID_MODULE_CATEGORIES:
    494             return True
    495         groupID = evetypes.GetGroupID(typeID)
    496         if groupID in VALID_CHARGE_GROUPS:
    497             return True
    498         if typeID in evetypes.GetTypeIDsByListID(evetypes.TYPE_LIST_FILAMENTS):
    499             return True
    500         if typeID in evetypes.GetTypeIDsByListID(evetypes.TYPE_LIST_OTHER_CARGO_HOLD_TYPES):
    501             return True
    502     if flagID == const.flagFighterBay:
    503         if categoryID == const.categoryFighter:
    504             return True
    505     return False
    506 
    507 
    508 allowedCategories = [const.categoryAccessories,
    509  const.categoryAncientRelic,
    510  const.categoryApparel,
    511  const.categoryAsteroid,
    512  const.categoryBlueprint,
    513  const.categoryCharge,
    514  const.categoryCommodity,
    515  const.categoryDecryptors,
    516  const.categoryDeployable,
    517  const.categoryDrone,
    518  const.categoryImplant,
    519  const.categoryMaterial,
    520  const.categoryModule,
    521  const.categoryPlanetaryCommodities,
    522  const.categoryPlanetaryInteraction,
    523  const.categoryPlanetaryResources,
    524  const.categoryShip,
    525  const.categoryShipSkin,
    526  const.categorySkill,
    527  const.categoryInfrastructureUpgrade,
    528  const.categoryStructureModule,
    529  const.categorySubSystem,
    530  const.categorySpecialEditionAssets,
    531  const.categoryFighter]
    532 
    533 def IsAllowedToAddAtAll(typeID):
    534     if dynamicitemattributes.IsDynamicType(typeID):
    535         return False
    536     categoryID = evetypes.GetCategoryID(typeID)
    537     return categoryID in allowedCategories