import csv import config # noinspection PyPackageRequirements import wx import wx.lib.agw.hypertreelist from gui.builtinItemStatsViews.helpers import AutoListCtrl from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from enum import Enum class AttrGrouping(Enum): # These are self-explanatory NORMAL = 1 RESIST = 2 SENSOR = 3 attr_order = { "Fighter": { "Fighters":{ AttrGrouping.NORMAL: [ "mass", "maxVelocity", "agility", "volume", "signatureRadius", "fighterSquadronMaxSize", "fighterSquadronOrbitRange", "fighterRefuelingTime", ] }, "Shield": { # break these up into various constants, since this can be used in fighters as well as ships maybe? AttrGrouping.NORMAL: [ "shieldCapacity", "shieldRechargeRate" ], AttrGrouping.RESIST: [ ("em", "shieldEmDamageResonance"), ("thermal","shieldExplosiveDamageResonance"), ("kinetic", "shieldKineticDamageResonance"), ("explosive", "shieldThermalDamageResonance") ] }, "Targeting": { AttrGrouping.NORMAL: [ "maxTargetRange", "maxLockedTargets", "scanRadarStrength", "scanLadarStrength", "scanMagnetometricStrength", "scanGravimetricStrength", "scanResolution", ] } }, 'default': { "Structure": { AttrGrouping.NORMAL: [ "hp", "capacity", "mass", "volume", "agility", "droneCapacity", "droneBandwidth", "specialOreHoldCapacity", "specialGasHoldCapacity", "specialMineralHoldCapacity", "specialSalvageHoldCapacity", "specialShipHoldCapacity", "specialSmallShipHoldCapacity", "specialMediumShipHoldCapacity", "specialLargeShipHoldCapacity", "specialIndustrialShipHoldCapacity", "specialAmmoHoldCapacity", "specialCommandCenterHoldCapacity", "specialPlanetaryCommoditiesHoldCapacity", "structureDamageLimit", "specialSubsystemHoldCapacity", ], AttrGrouping.RESIST: [ ("em", "emDamageResonance"), ("thermal", "thermalDamageResonance"), ("kinetic", "kineticDamageResonance"), ("explosive", "explosiveDamageResonance") ] }, "Armor": { AttrGrouping.NORMAL: [ "armorHP", "armorDamageLimit" ], AttrGrouping.RESIST: [ ("em","armorEmDamageResonance"), ("thermal","armorThermalDamageResonance"), ("kinetic", "armorKineticDamageResonance"), ("explosive","armorExplosiveDamageResonance") ] }, "Shield": { AttrGrouping.NORMAL: [ "shieldCapacity", "shieldRechargeRate", "shieldDamageLimit" ], AttrGrouping.RESIST: [ ("em", "shieldEmDamageResonance"), ("thermal", "shieldExplosiveDamageResonance"), ("kinetic", "shieldKineticDamageResonance"), ("explosive", "shieldThermalDamageResonance") ] }, "Electronic Resistances": { AttrGrouping.NORMAL: [ "ECMResistance", "remoteAssistanceImpedance", "remoteRepairImpedance", "energyWarfareResistance", "sensorDampenerResistance", "stasisWebifierResistance", "targetPainterResistance", "weaponDisruptionResistance", ] }, "Capacitor": { AttrGrouping.NORMAL: [ "capacitorCapacity", "rechargeRate", ] }, "Targeting": { AttrGrouping.NORMAL: [ "maxTargetRange", "maxRange", "maxLockedTargets", "signatureRadius", "optimalSigRadius", "scanResolution", "proximityRange", "falloff", "trackingSpeed", ], AttrGrouping.SENSOR: [ "scanLadarStrength", "scanMagnetometricStrength", "scanGravimetricStrength", "scanRadarStrength", ] }, "Shared Facilities": { AttrGrouping.NORMAL: ["shipMaintenanceBayCapacity", "fleetHangarCapacity", "maxJumpClones", ] }, "Fighter Squadron Facilities": { AttrGrouping.NORMAL: [ "fighterCapacity", "fighterTubes", "fighterLightSlots", "fighterSupportSlots", "fighterHeavySlots", "fighterStandupLightSlots", "fighterStandupSupportSlots", "fighterStandupHeavySlots", ] }, "On Death": { AttrGrouping.NORMAL: [ "onDeathDamageEM", "onDeathDamageTherm", "onDeathDamageKin", "onDeathDamageExp", "onDeathAOERadius", "onDeathSignatureRadius", ] }, "Jump Drive Systems": { AttrGrouping.NORMAL: [ "jumpDriveCapacitorNeed", "jumpDriveRange", "jumpDriveConsumptionType", "jumpDriveConsumptionAmount", "jumpPortalCapacitorNeed", "jumpDriveDuration", "specialFuelBayCapacity", "jumpPortalConsumptionMassFactor", "jumpPortalDuration", ] }, "Propulsion": { AttrGrouping.NORMAL: [ "maxVelocity" ] } } } class ItemParams(wx.Panel): def __init__(self, parent, stuff, item, context=None): wx.Panel.__init__(self, parent) mainSizer = wx.BoxSizer(wx.VERTICAL) self.paramList = wx.lib.agw.hypertreelist.HyperTreeList(self, wx.ID_ANY, agwStyle=wx.TR_HIDE_ROOT | wx.TR_NO_LINES | wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_HAS_BUTTONS) self.paramList.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) self.SetSizer(mainSizer) self.toggleView = 1 self.stuff = stuff self.item = item self.attrInfo = {} self.attrValues = {} self._fetchValues() self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) mainSizer.Add(self.m_staticline, 0, wx.EXPAND) bSizer = wx.BoxSizer(wx.HORIZONTAL) self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, " ", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, "Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, "Export Item Stats", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL) if stuff is not None: self.refreshBtn = wx.Button(self, wx.ID_ANY, "Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) self.PopulateList() self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats) def _fetchValues(self): if self.stuff is None: self.attrInfo.clear() self.attrValues.clear() self.attrInfo.update(self.item.attributes) self.attrValues.update(self.item.attributes) elif self.stuff.item == self.item: self.attrInfo.clear() self.attrValues.clear() self.attrInfo.update(self.stuff.item.attributes) self.attrValues.update(self.stuff.itemModifiedAttributes) elif self.stuff.charge == self.item: self.attrInfo.clear() self.attrValues.clear() self.attrInfo.update(self.stuff.charge.attributes) self.attrValues.update(self.stuff.chargeModifiedAttributes) # When item for stats window no longer exists, don't change anything else: return def UpdateList(self): self.Freeze() self.paramList.ClearAll() self.PopulateList() self.Thaw() self.paramList.resizeLastColumn(100) def RefreshValues(self, event): self._fetchValues() self.UpdateList() if event: event.Skip() def ToggleViewMode(self, event): self.toggleView *= -1 self.UpdateList() event.Skip() def ExportItemStats(self, event): exportFileName = self.item.name + " (" + str(self.item.ID) + ").csv" saveFileDialog = wx.FileDialog(self, "Save CSV file", "", exportFileName, "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if saveFileDialog.ShowModal() == wx.ID_CANCEL: return # the user hit cancel... with open(saveFileDialog.GetPath(), "w") as exportFile: writer = csv.writer(exportFile, delimiter=',') writer.writerow( [ "ID", "Internal Name", "Friendly Name", "Modified Value", "Base Value", ] ) for attribute in self.attrValues: try: attribute_id = self.attrInfo[attribute].ID except (KeyError, AttributeError): attribute_id = '' try: attribute_name = self.attrInfo[attribute].name except (KeyError, AttributeError): attribute_name = attribute try: attribute_displayname = self.attrInfo[attribute].displayName except (KeyError, AttributeError): attribute_displayname = '' try: attribute_value = self.attrInfo[attribute].value except (KeyError, AttributeError): attribute_value = '' try: attribute_modified_value = self.attrValues[attribute].value except (KeyError, AttributeError): attribute_modified_value = self.attrValues[attribute] writer.writerow( [ attribute_id, attribute_name, attribute_displayname, attribute_modified_value, attribute_value, ] ) def PopulateList(self): self.paramList.AddColumn("Attribute") self.paramList.AddColumn("Current Value") if self.stuff is not None: self.paramList.AddColumn("Base Value") self.paramList.SetMainColumn(0) # the one with the tree in it... self.paramList.SetColumnWidth(0, 175) root = self.paramList.AddRoot("The Root Item") # self.paramList.setResizeColumn(0) self.imageList = wx.ImageList(16, 16) self.paramList.AssignImageList(self.imageList) processed_attribs = set() misc_parent = root if self.item.category.categoryName in ("Ship", "Fighter"): order = attr_order.get(self.item.category.categoryName, attr_order.get("default")) # start building out the tree for heading, data in order.items(): header_item = self.paramList.AppendItem(root, heading) for attr in data.get(AttrGrouping.NORMAL, []): if attr in self.attrValues: attrIcon, attrName, currentVal, baseVal = self.GetData(attr) attr_item = self.paramList.AppendItem(header_item, attrName) self.paramList.SetItemText(attr_item , currentVal, 1) if self.stuff is not None: self.paramList.SetItemText(attr_item , baseVal, 2) self.paramList.SetItemImage(attr_item , attrIcon, which=wx.TreeItemIcon_Normal) attr_item.SetTextX(-100) print("{} has x: {}".format(attrName, attr_item.GetTextX())) processed_attribs.add(attr) resists = data.get(AttrGrouping.RESIST, []) if len(resists) > 0: resist_item = self.paramList.AppendItem(header_item, "Resistances") for _, attr in data.get(AttrGrouping.RESIST, []): if attr in self.attrValues: attrIcon, attrName, currentVal, baseVal = self.GetData(attr) attr_item = self.paramList.AppendItem(resist_item , attrName) self.paramList.SetItemText(attr_item , currentVal, 1) if self.stuff is not None: self.paramList.SetItemText(attr_item , baseVal, 2) self.paramList.SetItemImage(attr_item , attrIcon, which=wx.TreeItemIcon_Normal) processed_attribs.add(attr) print("{} has x: {}".format(attrName, attr_item.GetTextX())) self.paramList.Expand(resist_item) sensors = data.get(AttrGrouping.SENSOR, []) if len(sensors) > 0: sensor_item = self.paramList.AppendItem(header_item, "Sensor Strengths") for attr in data.get(AttrGrouping.SENSOR, []): if attr in self.attrValues: attrIcon, attrName, currentVal, baseVal = self.GetData(attr) attr_item = self.paramList.AppendItem(sensor_item, attrName) self.paramList.SetItemText(attr_item, currentVal, 1) if self.stuff is not None: self.paramList.SetItemText(attr_item, baseVal, 2) self.paramList.SetItemImage(attr_item, attrIcon, which=wx.TreeItemIcon_Normal) processed_attribs.add(attr) print("{} has x: {}".format(attrName, attr_item.GetTextX())) self.paramList.Expand(sensor_item) self.paramList.Expand(header_item) misc_parent = self.paramList.AppendItem(root, "Miscellaneous") names = list(self.attrValues.keys()) names.sort() idNameMap = {} idCount = 0 for name in names: if name in processed_attribs: continue attrIcon, attrName, currentVal, baseVal = self.GetData(name) attr_item = self.paramList.AppendItem(misc_parent, attrName) self.paramList.SetItemText(attr_item, currentVal, 1) if self.stuff is not None: self.paramList.SetItemText(attr_item, baseVal, 2) self.paramList.SetItemImage(attr_item, attrIcon, which=wx.TreeItemIcon_Normal) self.paramList.Expand(misc_parent) # @todo: pheonix, this lamda used cmp() which no longer exists in py3. Probably a better way to do this in the # long run, take a look # self.paramList.SortItems(lambda id1, id2: (idNameMap[id1] > idNameMap[id2]) - (idNameMap[id1] < idNameMap[id2])) # self.paramList.RefreshRows() self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) self.Layout() def GetData(self, attr): info = self.attrInfo.get(attr) att = self.attrValues[attr] # If we're working with a stuff object, we should get the original value from our getBaseAttrValue function, # which will return the value with respect to the effective base (with mutators / overrides in place) valDefault = getattr(info, "value", None) # Get default value from attribute if self.stuff is not None: # if it's a stuff, overwrite default (with fallback to current value) valDefault = self.stuff.getBaseAttrValue(attr, valDefault) valueDefault = valDefault if valDefault is not None else att val = getattr(att, "value", None) value = val if val is not None else att if info and info.displayName and self.toggleView == 1: attrName = info.displayName else: attrName = attr if info and config.debug: attrName += " ({})".format(info.ID) if info: if info.iconID is not None: iconFile = info.iconID icon = BitmapLoader.getBitmap(iconFile, "icons") if icon is None: icon = BitmapLoader.getBitmap("transparent16x16", "gui") attrIcon = self.imageList.Add(icon) else: attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons")) else: attrIcon = self.imageList.Add(BitmapLoader.getBitmap("0", "icons")) # index = self.paramList.AppendItem(root, attrName) # idNameMap[idCount] = attrName # self.paramList.SetPyData(index, idCount) # idCount += 1 if self.toggleView != 1: valueUnit = str(value) elif info and info.unit: valueUnit = self.FormatValue(*info.unit.TranslateValue(value)) else: valueUnit = formatAmount(value, 3, 0, 0) if self.toggleView != 1: valueUnitDefault = str(valueDefault) elif info and info.unit: valueUnitDefault = self.FormatValue(*info.unit.TranslateValue(valueDefault)) else: valueUnitDefault = formatAmount(valueDefault, 3, 0, 0) return (attrIcon, attrName, valueUnit, valueUnitDefault) # self.paramList.SetItemText(index, valueUnit, 1) # if self.stuff is not None: # self.paramList.SetItemText(index, valueUnitDefault, 2) # self.paramList.SetItemImage(index, attrIcon, which=wx.TreeItemIcon_Normal) @staticmethod def FormatValue(value, unit): """Formats a value / unit combination into a string @todo: move this to a more central location, since this is also used in the item mutator panel""" if isinstance(value, (int, float)): fvalue = formatAmount(value, 3, 0, 0) else: fvalue = value return "%s %s" % (fvalue, unit) if __name__ == "__main__": import eos.db # need to set up some paths, since bitmap loader requires config to have things # Should probably change that so that it's not dependant on config import os os.chdir('..') import config config.defPaths(None) class Frame(wx.Frame): def __init__(self, title): super().__init__(None, title=title, size=(1000, 500)) if 'wxMSW' in wx.PlatformInfo: color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) self.SetBackgroundColour(color) main_sizer = wx.BoxSizer(wx.HORIZONTAL) item = eos.db.getItem(22452) # Ragnarok panel = ItemParams(self, None, item) main_sizer.Add(panel, 1, wx.EXPAND | wx.ALL, 2) self.SetSizer(main_sizer) app = wx.App(redirect=False) # Error messages go to popup window top = Frame("Test Item Attributes") top.Show() app.MainLoop()