diff --git a/eos/capSim.py b/eos/capSim.py index 06737b58b..c4a106437 100644 --- a/eos/capSim.py +++ b/eos/capSim.py @@ -148,6 +148,7 @@ class CapSimulator: stability_precision = self.stability_precision period = self.period + activation = None iterations = 0 capCapacity = self.capacitorCapacity @@ -162,7 +163,12 @@ class CapSimulator: t_max = self.t_max while 1: - activation = pop(state) + # Nothing to pop - might happen when no mods are activated, or when + # only cap injectors are active (and are postponed by code below) + try: + activation = pop(state) + except IndexError: + break t_now, duration, capNeed, shot, clipSize, reloadTime, isInjector = activation # Max time reached, stop simulation - we're stable @@ -275,7 +281,8 @@ class CapSimulator: activation[3] = shot push(state, activation) - push(state, activation) + if activation is not None: + push(state, activation) # update instance with relevant results. self.t = t_last diff --git a/eos/effects.py b/eos/effects.py index 73d311d4c..730454a12 100644 --- a/eos/effects.py +++ b/eos/effects.py @@ -4261,7 +4261,7 @@ class Effect1434(BaseEffect): for sensorType in ('Gravimetric', 'Ladar', 'Magnetometric', 'Radar'): fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill('Electronic Warfare'), 'scan{0}StrengthBonus'.format(sensorType), - ship.getModifiedItemAttr('shipBonusCB'), stackingPenalties=True, + ship.getModifiedItemAttr('shipBonusCB'), skill='Caldari Battleship', **kwargs) @@ -5967,8 +5967,10 @@ class Effect2019(BaseEffect): @staticmethod def handler(fit, container, context, **kwargs): level = container.level if 'skill' in context else 1 + penalized = False if 'skill' in context else True fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == 'Logistic Drone', - 'shieldBonus', container.getModifiedItemAttr('damageHP') * level, **kwargs) + 'shieldBonus', container.getModifiedItemAttr('damageHP') * level, + stackingPenalties=penalized, **kwargs) class Effect2020(BaseEffect): @@ -6721,10 +6723,10 @@ class Effect2251(BaseEffect): @staticmethod def handler(fit, src, context, **kwargs): - fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupActive', - src.getModifiedItemAttr('maxGangModules'), **kwargs) fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupOnline', src.getModifiedItemAttr('maxGangModules'), **kwargs) + fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupActive', + src.getModifiedItemAttr('maxGangModules'), **kwargs) class Effect2252(BaseEffect): @@ -29503,6 +29505,8 @@ class Effect6613(BaseEffect): @staticmethod def handler(fit, src, context, **kwargs): + fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupOnline', + src.getModifiedItemAttr('shipBonusRole1'), **kwargs) fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupActive', src.getModifiedItemAttr('shipBonusRole1'), **kwargs) @@ -29605,6 +29609,8 @@ class Effect6619(BaseEffect): @staticmethod def handler(fit, src, context, **kwargs): + fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupOnline', + src.getModifiedItemAttr('shipBonusRole1'), **kwargs) fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupActive', src.getModifiedItemAttr('shipBonusRole1'), **kwargs) @@ -29917,6 +29923,8 @@ class Effect6640(BaseEffect): @staticmethod def handler(fit, src, context, **kwargs): + fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupOnline', + src.getModifiedItemAttr('shipBonusRole1'), **kwargs) fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill('Leadership'), 'maxGroupActive', src.getModifiedItemAttr('shipBonusRole1'), **kwargs) diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 893492f39..7be24ff76 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -29,6 +29,7 @@ from eos.db.gamedata.queries import getAttributeInfo defaultValuesCache = {} cappingAttrKeyCache = {} +resistanceCache = {} def getAttrDefault(key, fallback=None): @@ -46,19 +47,23 @@ def getAttrDefault(key, fallback=None): def getResistanceAttrID(modifyingItem, effect): - # If it doesn't exist on the effect, check the modifying modules attributes. If it's there, set it on the - # effect for this session so that we don't have to look here again (won't always work when it's None, but - # will catch most) - if not effect.getattr('resistanceCalculated'): + # If it doesn't exist on the effect, check the modifying module's attributes. + # If it's there, cache it and return + if effect.resistanceID: + return effect.resistanceID + cacheKey = (modifyingItem.item.ID, effect.ID) + try: + return resistanceCache[cacheKey] + except KeyError: attrPrefix = effect.getattr('prefix') if attrPrefix: - effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None - if not effect.resistanceID: - effect.resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None + resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None + if not resistanceID: + resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None else: - effect.resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None - effect.resistanceCalculated = True - return effect.resistanceID + resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None + resistanceCache[cacheKey] = resistanceID + return resistanceID class ItemAttrShortcut: @@ -553,10 +558,12 @@ class ModifiedAttributeDict(collections.MutableMapping): if 'projected' not in effectType: return 1 remoteResistID = getResistanceAttrID(modifyingItem=fit.getModifier(), effect=effect) + if not remoteResistID: + return 1 attrInfo = getAttributeInfo(remoteResistID) # Get the attribute of the resist resist = fit.ship.itemModifiedAttributes[attrInfo.attributeName] or None - return resist or 1.0 + return resist or 1 class Affliction: diff --git a/graphs/gui/canvasPanel.py b/graphs/gui/canvasPanel.py index fc649df2e..3b846b507 100644 --- a/graphs/gui/canvasPanel.py +++ b/graphs/gui/canvasPanel.py @@ -188,6 +188,7 @@ class GraphCanvasPanel(wx.Panel): if minX is not None and maxX is not None: minY = min(allYs, default=None) maxY = max(allYs, default=None) + yDiff = (maxY or 0) - (minY or 0) xMark = max(min(self.xMark, maxX), minX) # If in top 10% of X coordinates, align labels differently if xMark > canvasMinX + 0.9 * (canvasMaxX - canvasMinX): @@ -214,7 +215,12 @@ class GraphCanvasPanel(wx.Panel): def addYMark(val): if val is None: return - rounded = roundToPrec(val, 4) + # Round according to shown Y range - the bigger the range, + # the rougher the rounding + if yDiff != 0: + rounded = roundToPrec(val, 4, nsValue=yDiff) + else: + rounded = val # If due to some bug or insufficient plot density we're # out of bounds, do not add anything if minY <= val <= maxY or minY <= rounded <= maxY: diff --git a/graphs/gui/frame.py b/graphs/gui/frame.py index 1ce31543f..cfb311edb 100644 --- a/graphs/gui/frame.py +++ b/graphs/gui/frame.py @@ -38,6 +38,9 @@ from .ctrlPanel import GraphControlPanel pyfalog = Logger(__name__) +REDRAW_DELAY = 500 + + class GraphFrame(AuxiliaryFrame): def __init__(self, parent): @@ -45,7 +48,7 @@ class GraphFrame(AuxiliaryFrame): pyfalog.warning('Matplotlib is not enabled. Skipping initialization.') return - super().__init__(parent, title='Graphs', style=wx.RESIZE_BORDER, size=(520, 390)) + super().__init__(parent, title='Graphs', size=(520, 390), resizeable=True) self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.SetIcon(wx.Icon(BitmapLoader.getBitmap('graphs_small', 'gui'))) @@ -90,6 +93,9 @@ class GraphFrame(AuxiliaryFrame): self.mainFrame.Bind(GE.GRAPH_OPTION_CHANGED, self.OnGraphOptionChanged) self.mainFrame.Bind(GE.EFFECTIVE_HP_TOGGLED, self.OnEffectiveHpToggled) + self.drawTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnDrawTimer, self.drawTimer) + self.Layout() self.UpdateWindowSize() self.draw() @@ -128,7 +134,10 @@ class GraphFrame(AuxiliaryFrame): for fitID in event.fitIDs: self.clearCache(reason=GraphCacheCleanupReason.fitChanged, extraData=fitID) self.ctrlPanel.OnFitChanged(event) - self.draw() + # Data has to be recalculated - delay redraw + # to give time to finish UI update in main window + self.drawTimer.Stop() + self.drawTimer.Start(REDRAW_DELAY, True) def OnFitRemoved(self, event): event.Skip() @@ -184,7 +193,10 @@ class GraphFrame(AuxiliaryFrame): self.ctrlPanel.refreshAxeLabels(restoreSelection=True) self.Layout() self.clearCache(reason=GraphCacheCleanupReason.hpEffectivityChanged) - self.draw() + # Data has to be recalculated - delay redraw + # to give time to finish UI update in main window + self.drawTimer.Stop() + self.drawTimer.Start(REDRAW_DELAY, True) # Even if graph is not selected, keep it updated for idx in range(self.graphSelection.GetCount()): view = self.getView(idx=idx) @@ -202,6 +214,10 @@ class GraphFrame(AuxiliaryFrame): self.draw() event.Skip() + def OnDrawTimer(self, event): + event.Skip() + self.draw() + def OnClose(self, event): self.mainFrame.Unbind(GE.FIT_RENAMED, handler=self.OnFitRenamed) self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.OnFitChanged) diff --git a/gui/auxFrame.py b/gui/auxFrame.py index 30237cfac..a751737be 100644 --- a/gui/auxFrame.py +++ b/gui/auxFrame.py @@ -26,8 +26,10 @@ class AuxiliaryFrame(wx.Frame): _instance = None - def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None): + def __init__(self, parent, id=None, title=None, pos=None, size=None, style=None, name=None, resizeable=False): baseStyle = wx.FRAME_NO_TASKBAR | wx.FRAME_FLOAT_ON_PARENT | wx.CAPTION | wx.CLOSE_BOX | wx.SYSTEM_MENU + if resizeable: + baseStyle = baseStyle | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX kwargs = { 'parent': parent, 'style': baseStyle if style is None else baseStyle | style} diff --git a/gui/builtinContextMenus/itemVariationChange.py b/gui/builtinContextMenus/itemVariationChange.py index 290606e25..5c2e110c3 100644 --- a/gui/builtinContextMenus/itemVariationChange.py +++ b/gui/builtinContextMenus/itemVariationChange.py @@ -51,6 +51,7 @@ class ChangeItemToVariation(ContextMenuCombined): def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem): self.moduleLookup = {} sFit = Fit.getInstance() + sMkt = Market.getInstance() fit = sFit.getFit(self.mainFrame.getActiveFit()) def get_metalevel(x): @@ -61,7 +62,8 @@ class ChangeItemToVariation(ContextMenuCombined): def get_metagroup(x): # We want deadspace before officer mods remap = {5: 6, 6: 5} - return remap.get(x.metaGroup.ID, x.metaGroup.ID) if x.metaGroup is not None else 0 + metaGroup = sMkt.getMetaGroupByItem(x) + return remap.get(metaGroup.ID, metaGroup.ID) if metaGroup is not None else 0 def get_boosterrank(x): # If we're returning a lot of items, sort my name @@ -84,7 +86,9 @@ class ChangeItemToVariation(ContextMenuCombined): bindmenu = m # Do not show abyssal items - items = list(i for i in self.mainVariations if i.metaGroup is None or i.metaGroup.ID != 15) + items = list( + i for i in self.mainVariations + if sMkt.getMetaGroupByItem(i) is None or sMkt.getMetaGroupByItem(i).ID != 15) # Sort items by metalevel, and group within that metalevel # Sort all items by name first items.sort(key=lambda x: x.name) @@ -102,12 +106,13 @@ class ChangeItemToVariation(ContextMenuCombined): group = None for item in items: # Apparently no metaGroup for the Tech I variant: + metaGroup = sMkt.getMetaGroupByItem(item) if 'subSystem' in item.effects: thisgroup = item.marketGroup.marketGroupName - elif item.metaGroup is None: + elif metaGroup is None: thisgroup = 'Tech I' else: - thisgroup = item.metaGroup.name + thisgroup = metaGroup.name if thisgroup != group and context not in ('implantItem', 'boosterItem'): group = thisgroup diff --git a/gui/builtinViewColumns/attributeDisplay.py b/gui/builtinViewColumns/attributeDisplay.py index b36cf7132..e3f074672 100644 --- a/gui/builtinViewColumns/attributeDisplay.py +++ b/gui/builtinViewColumns/attributeDisplay.py @@ -84,11 +84,12 @@ class AttributeDisplay(ViewColumn): return "" if self.info.name == "volume": - str_ = (formatAmount(attr, 3, 0, 3)) - if hasattr(mod, "amount"): - str_ += "m\u00B3 (%s m\u00B3)" % (formatAmount(attr * mod.amount, 3, 0, 3)) - attr = str_ - + if getattr(mod, "amount", 1) != 1: + attr = "{} m\u00B3 ({} m\u00B3)".format( + formatAmount(attr, 3, 0, 6), + formatAmount(attr * mod.amount, 3, 0, 6)) + else: + attr = "{} m\u00B3".format(formatAmount(attr, 3, 0, 6)) if isinstance(attr, (float, int)): attr = (formatAmount(attr, 3, 0, 3)) diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index 77b251aa7..7a10cb621 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -33,6 +33,7 @@ from eos.saveddata.module import Module, Rack from eos.saveddata.targetProfile import TargetProfile from graphs.wrapper import BaseWrapper from gui.builtinContextMenus.envEffectAdd import AddEnvironmentEffect +from gui.utils.numberFormatter import formatAmount from gui.viewColumn import ViewColumn from service.fit import Fit as FitSvc from service.market import Market @@ -64,7 +65,11 @@ class BaseName(ViewColumn): return "%d/%d %s" % \ (stuff.amount, stuff.getModifiedItemAttr("fighterSquadronMaxSize"), stuff.item.name) elif isinstance(stuff, Cargo): - return "%dx %s" % (stuff.amount, stuff.item.name) + if stuff.item.group.name in ("Cargo Container", "Secure Cargo Container", "Audit Log Secure Container", "Freight Container"): + capacity = stuff.item.getAttribute('capacity') + if capacity: + return "{:d}x {} ({} m\u00B3)".format(stuff.amount, stuff.item.name, formatAmount(capacity, 3, 0, 6)) + return "{:d}x {}".format(stuff.amount, stuff.item.name) elif isinstance(stuff, Fit): if self.projectedView: # we need a little more information for the projected view diff --git a/gui/characterEditor.py b/gui/characterEditor.py index 05dab9638..ae26fcaa8 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -154,7 +154,9 @@ class CharacterEntityEditor(EntityEditor): class CharacterEditor(AuxiliaryFrame): def __init__(self, parent): - super().__init__(parent, id=wx.ID_ANY, title="Character Editor", pos=wx.DefaultPosition, size=wx.Size(640, 600)) + super().__init__( + parent, id=wx.ID_ANY, title="Character Editor", resizeable=True, + pos=wx.DefaultPosition, size=wx.Size(640, 600)) i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui")) self.SetIcon(i) diff --git a/gui/devTools.py b/gui/devTools.py index 5abd4d80c..3e5ac725e 100644 --- a/gui/devTools.py +++ b/gui/devTools.py @@ -39,7 +39,7 @@ class DevTools(AuxiliaryFrame): def __init__(self, parent): super().__init__( - parent, id=wx.ID_ANY, title="Development Tools", style=wx.RESIZE_BORDER, + parent, id=wx.ID_ANY, title="Development Tools", resizeable=True, size=wx.Size(400, 320) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240)) self.mainFrame = parent self.block = False diff --git a/gui/esiFittings.py b/gui/esiFittings.py index 0a0e4e1db..a94c362e5 100644 --- a/gui/esiFittings.py +++ b/gui/esiFittings.py @@ -25,7 +25,7 @@ class EveFittings(AuxiliaryFrame): def __init__(self, parent): super().__init__( parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, - size=wx.Size(750, 450), style=wx.RESIZE_BORDER) + size=wx.Size(750, 450), resizeable=True) self.mainFrame = parent mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -204,7 +204,7 @@ class ExportToEve(AuxiliaryFrame): def __init__(self, parent): super().__init__( parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, - size=wx.Size(400, 120) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 100), style=wx.RESIZE_BORDER) + size=wx.Size(400, 120) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 100), resizeable=True) self.mainFrame = parent @@ -317,7 +317,7 @@ class SsoCharacterMgmt(AuxiliaryFrame): def __init__(self, parent): super().__init__( parent, id=wx.ID_ANY, title="SSO Character Management", pos=wx.DefaultPosition, - size=wx.Size(550, 250), style=wx.RESIZE_BORDER) + size=wx.Size(550, 250), resizeable=True) self.mainFrame = parent mainSizer = wx.BoxSizer(wx.HORIZONTAL) diff --git a/gui/itemStats.py b/gui/itemStats.py index e9bb14cec..62667befe 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -56,7 +56,7 @@ class ItemStatsFrame(AuxiliaryFrame): title="Item stats", pos=pos, size=size, - style=wx.RESIZE_BORDER) + resizeable=True) empty = getattr(victim, "isEmpty", False) diff --git a/gui/patternEditor.py b/gui/patternEditor.py index 543b5d71c..9ce5e7c2c 100644 --- a/gui/patternEditor.py +++ b/gui/patternEditor.py @@ -94,7 +94,7 @@ class DmgPatternEditor(AuxiliaryFrame): def __init__(self, parent): super().__init__( - parent, id=wx.ID_ANY, title="Damage Pattern Editor", style=wx.RESIZE_BORDER, + parent, id=wx.ID_ANY, title="Damage Pattern Editor", resizeable=True, # Dropdown list widget is scaled to its longest content line on GTK, adapt to that size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240)) diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index cc71c8886..d5b827bbf 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -24,7 +24,7 @@ class AttributeEditor(AuxiliaryFrame): def __init__(self, parent): super().__init__( parent, wx.ID_ANY, title="Attribute Editor", pos=wx.DefaultPosition, - size=wx.Size(650, 600), style=wx.RESIZE_BORDER) + size=wx.Size(650, 600), resizeable=True) i = wx.Icon(BitmapLoader.getBitmap("fit_rename_small", "gui")) self.SetIcon(i) diff --git a/gui/setEditor.py b/gui/setEditor.py index ba6443d8e..5af95f35b 100644 --- a/gui/setEditor.py +++ b/gui/setEditor.py @@ -119,7 +119,7 @@ class ImplantSetEditor(AuxiliaryFrame): def __init__(self, parent): super().__init__( - parent, id=wx.ID_ANY, title="Implant Set Editor", style=wx.RESIZE_BORDER, + parent, id=wx.ID_ANY, title="Implant Set Editor", resizeable=True, size=wx.Size(950, 500) if "wxGTK" in wx.PlatformInfo else wx.Size(850, 420)) self.block = False diff --git a/gui/targetProfileEditor.py b/gui/targetProfileEditor.py index 7ae5b4aa5..0112ef73f 100644 --- a/gui/targetProfileEditor.py +++ b/gui/targetProfileEditor.py @@ -126,7 +126,7 @@ class TargetProfileEditor(AuxiliaryFrame): def __init__(self, parent): super().__init__( - parent, id=wx.ID_ANY, title="Target Profile Editor", style=wx.RESIZE_BORDER, + parent, id=wx.ID_ANY, title="Target Profile Editor", resizeable=True, # Dropdown list widget is scaled to its longest content line on GTK, adapt to that size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 240)) diff --git a/gui/utils/numberFormatter.py b/gui/utils/numberFormatter.py index 12d53a564..617e9e18f 100644 --- a/gui/utils/numberFormatter.py +++ b/gui/utils/numberFormatter.py @@ -99,14 +99,15 @@ def formatAmount(val, prec=3, lowest=0, highest=0, currency=False, forceSign=Fal return result -def roundToPrec(val, prec): +def roundToPrec(val, prec, nsValue=None): + """ + nsValue: custom value which should be used to determine normalization shift + """ # We're not rounding integers anyway # Also make sure that we do not ask to calculate logarithm of zero if int(val) == val: return int(val) - # Find round factor, taking into consideration that we want to keep at least prec - # positions for fractions with zero integer part (e.g. 0.0000354 for prec=3) - roundFactor = int(prec - math.ceil(math.log10(abs(val)))) + roundFactor = int(prec - math.floor(math.log10(abs(val if nsValue is None else nsValue))) - 1) # But we don't want to round integers if roundFactor < 0: roundFactor = 0 diff --git a/service/market.py b/service/market.py index 58477b946..b37ca282a 100644 --- a/service/market.py +++ b/service/market.py @@ -305,9 +305,10 @@ class Market: self.ITEMS_FORCEDMARKETGROUP_R = self.__makeRevDict(self.ITEMS_FORCEDMARKETGROUP) self.FORCEDMARKETGROUP = { - 685: False, # Ship Equipment > Electronic Warfare > ECCM - 681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays - 1639: False # Ship Equipment > Fleet Assistance > Command Processors + 685: False, # Ship Equipment > Electronic Warfare > ECCM + 681: False, # Ship Equipment > Electronic Warfare > Sensor Backup Arrays + 1639: False, # Ship Equipment > Fleet Assistance > Command Processors + 2527: True, # Ship Equipment > Hull & Armor > Mutadaptive Remote Armor Repairers - has hasTypes set to 1 while actually having no types } # Misc definitions @@ -328,7 +329,7 @@ class Market: "Structure", "Structure Module", ) - self.SEARCH_GROUPS = ("Ice Product",) + self.SEARCH_GROUPS = ("Ice Product", "Cargo Container", "Secure Cargo Container", "Audit Log Secure Container", "Freight Container") self.ROOT_MARKET_GROUPS = (9, # Ship Equipment 1111, # Rigs 157, # Drones