244 lines
9.2 KiB
Python
244 lines
9.2 KiB
Python
# noinspection PyPackageRequirements
|
|
|
|
import wx
|
|
|
|
import gui.mainFrame
|
|
from gui.auxWindow import AuxiliaryDialog
|
|
from gui.contextMenu import ContextMenuSingle
|
|
from service.ammo import Ammo
|
|
from service.market import Market
|
|
|
|
_t = wx.GetTranslation
|
|
|
|
|
|
class GraphFitAmmoPicker(ContextMenuSingle):
|
|
|
|
def __init__(self):
|
|
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
|
|
|
def display(self, callingWindow, srcContext, mainItem):
|
|
if srcContext != 'graphFitList':
|
|
return False
|
|
if mainItem is None or not mainItem.isFit:
|
|
return False
|
|
if callingWindow.graphFrame.getView().internalName != 'dmgStatsGraph':
|
|
return False
|
|
return True
|
|
|
|
def getText(self, callingWindow, itmContext, mainItem):
|
|
return _t('Plot with Different Ammo...')
|
|
|
|
def activate(self, callingWindow, fullContext, mainItem, i):
|
|
AmmoPickerFrame.openOne(callingWindow, mainItem.item, forceReopen=True)
|
|
|
|
|
|
# GraphFitAmmoPicker.register()
|
|
|
|
|
|
class AmmoPickerFrame(AuxiliaryDialog):
|
|
|
|
def __init__(self, parent, fit):
|
|
super().__init__(parent, title='Choose Different Ammo', style=wx.DEFAULT_DIALOG_STYLE, resizeable=True)
|
|
padding = 5
|
|
|
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
contents = AmmoPickerContents(self, fit)
|
|
mainSizer.Add(contents, 1, wx.EXPAND | wx.ALL, padding)
|
|
|
|
buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
|
|
if buttonSizer:
|
|
mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, padding)
|
|
|
|
self.SetSizer(mainSizer)
|
|
self.Layout()
|
|
|
|
contW, contH = contents.GetVirtualSize()
|
|
bestW = contW + padding * 2
|
|
bestH = contH + padding * 2
|
|
if buttonSizer:
|
|
# Yeah right... whatever
|
|
buttW, buttH = buttonSizer.GetSize()
|
|
bestW = max(bestW, buttW + padding * 2)
|
|
bestH += buttH + padding * 2
|
|
bestW = min(1000, bestW)
|
|
bestH = min(700, bestH)
|
|
self.SetSize(bestW, bestH)
|
|
self.SetMinSize(wx.Size(int(bestW * 0.7), int(bestH * 0.7)))
|
|
self.CenterOnParent()
|
|
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
|
|
|
|
def kbEvent(self, event):
|
|
if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
|
|
self.Close()
|
|
return
|
|
event.Skip()
|
|
|
|
|
|
class AmmoPickerContents(wx.ScrolledCanvas):
|
|
indent = 15
|
|
|
|
def __init__(self, parent, fit):
|
|
wx.ScrolledCanvas.__init__(self, parent)
|
|
self.SetScrollRate(0, 15)
|
|
|
|
mods = self.getMods(fit)
|
|
drones = self.getDrones(fit)
|
|
fighters = self.getFighters(fit)
|
|
self.rbLabelMap = {}
|
|
self.rbCheckboxMap = {}
|
|
|
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
moduleSizer = wx.BoxSizer(wx.VERTICAL)
|
|
mainSizer.Add(moduleSizer, 0, wx.ALL, 0)
|
|
|
|
self.droneSizer = wx.BoxSizer(wx.VERTICAL)
|
|
mainSizer.Add(self.droneSizer, 0, wx.ALL, 0)
|
|
|
|
fighterSizer = wx.BoxSizer(wx.VERTICAL)
|
|
mainSizer.Add(fighterSizer, 0, wx.ALL, 0)
|
|
|
|
firstRadio = True
|
|
|
|
for modInfo, modAmmo in mods:
|
|
text = '\n'.join('{}x {}'.format(amount, item.name) for item, amount in modInfo)
|
|
modRb = self.addRadioButton(moduleSizer, text, firstRadio)
|
|
firstRadio = False
|
|
# Get actual module, as ammo getters need it
|
|
mod = next((m for m in fit.modules if m.itemID == next(iter(modInfo))[0].ID), None)
|
|
_, ammoTree = Ammo.getInstance().getModuleStructuredAmmo(mod)
|
|
if len(ammoTree) == 1:
|
|
for ammoCatName, ammos in ammoTree.items():
|
|
for ammo in ammos:
|
|
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
|
else:
|
|
for ammoCatName, ammos in ammoTree.items():
|
|
if len(ammos) == 1:
|
|
ammo = next(iter(ammos))
|
|
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
|
|
else:
|
|
self.addLabel(moduleSizer, '{}:'.format(ammoCatName), modRb, indentLvl=1)
|
|
for ammo in ammos:
|
|
self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=2)
|
|
if drones:
|
|
droneRb = self.addRadioButton(self.droneSizer, 'Drones', firstRadio)
|
|
from gui.builtinAdditionPanes.droneView import DroneView
|
|
for drone in sorted(drones, key=DroneView.droneKey):
|
|
self.addCheckbox(self.droneSizer, '{}x {}'.format(drone.amount, drone.item.name), droneRb, indentLvl=1)
|
|
addBtn = wx.Button(self, wx.ID_ANY, '+', style=wx.BU_EXACTFIT)
|
|
addBtn.Bind(wx.EVT_BUTTON, self.OnDroneGroupAdd)
|
|
mainSizer.Add(addBtn, 0, wx.LEFT, self.indent)
|
|
if fighters:
|
|
fighterRb = self.addRadioButton(fighterSizer, 'Fighters', firstRadio)
|
|
from gui.builtinAdditionPanes.fighterView import FighterDisplay
|
|
for fighter in sorted(fighters, key=FighterDisplay.fighterKey):
|
|
self.addCheckbox(fighterSizer, '{}x {}'.format(fighter.amount, fighter.item.name), fighterRb, indentLvl=1)
|
|
|
|
self.SetSizer(mainSizer)
|
|
self.refreshStatus()
|
|
|
|
def addRadioButton(self, sizer, text, firstRadio=False):
|
|
if firstRadio:
|
|
rb = wx.RadioButton(self, wx.ID_ANY, text, style=wx.RB_GROUP)
|
|
rb.SetValue(True)
|
|
else:
|
|
rb = wx.RadioButton(self, wx.ID_ANY, text)
|
|
rb.SetValue(False)
|
|
rb.Bind(wx.EVT_RADIOBUTTON, self.rbSelected)
|
|
sizer.Add(rb, 0, wx.EXPAND | wx.ALL, 0)
|
|
return rb
|
|
|
|
def addCheckbox(self, sizer, text, currentRb, indentLvl=0):
|
|
cb = wx.CheckBox(self, -1, text)
|
|
sizer.Add(cb, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
|
if currentRb is not None:
|
|
self.rbCheckboxMap.setdefault(currentRb, []).append(cb)
|
|
|
|
def addLabel(self, sizer, text, currentRb, indentLvl=0):
|
|
text = text[0].capitalize() + text[1:]
|
|
label = wx.StaticText(self, wx.ID_ANY, text)
|
|
sizer.Add(label, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
|
|
if currentRb is not None:
|
|
self.rbLabelMap.setdefault(currentRb, []).append(label)
|
|
|
|
def getMods(self, fit):
|
|
sMkt = Market.getInstance()
|
|
sAmmo = Ammo.getInstance()
|
|
loadableChargesCache = {}
|
|
# Modules, format: {frozenset(ammo): {item: count}}
|
|
modsPrelim = {}
|
|
if fit is not None:
|
|
for mod in fit.modules:
|
|
if not mod.canDealDamage():
|
|
continue
|
|
typeID = mod.item.ID
|
|
if typeID not in loadableChargesCache:
|
|
loadableChargesCache[typeID] = sAmmo.getModuleFlatAmmo(mod)
|
|
charges = loadableChargesCache[typeID]
|
|
# We're not interested in modules which contain no charges
|
|
if charges:
|
|
data = modsPrelim.setdefault(frozenset(charges), {})
|
|
if mod.item not in data:
|
|
data[mod.item] = 0
|
|
data[mod.item] += 1
|
|
# Format: [([(item, count), ...], frozenset(ammo)), ...]
|
|
modsFinal = []
|
|
for charges, itemCounts in modsPrelim.items():
|
|
modsFinal.append((
|
|
# Sort items within group
|
|
sorted(itemCounts.items(), key=lambda i: sMkt.itemSort(i[0], reverseMktGrp=True), reverse=True),
|
|
charges))
|
|
# Sort item groups
|
|
modsFinal.sort(key=lambda i: sMkt.itemSort(i[0][0][0], reverseMktGrp=True), reverse=True)
|
|
return modsFinal
|
|
|
|
def getDrones(self, fit):
|
|
drones = []
|
|
if fit is not None:
|
|
for drone in fit.drones:
|
|
if drone.item is None:
|
|
continue
|
|
# Drones are our "ammo", so we want to pick even those which are inactive
|
|
if drone.canDealDamage(ignoreState=True):
|
|
drones.append(drone)
|
|
continue
|
|
if {'remoteWebifierEntity', 'remoteTargetPaintEntity'}.intersection(drone.item.effects):
|
|
drones.append(drone)
|
|
continue
|
|
return drones
|
|
|
|
def getFighters(self, fit):
|
|
fighters = []
|
|
if fit is not None:
|
|
for fighter in fit.fighters:
|
|
if fighter.item is None:
|
|
continue
|
|
# Fighters are our "ammo" as well
|
|
if fighter.canDealDamage(ignoreState=True):
|
|
fighters.append(fighter)
|
|
continue
|
|
for ability in fighter.abilities:
|
|
if not ability.active:
|
|
continue
|
|
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
|
fighters.append(fighter)
|
|
break
|
|
return fighters
|
|
|
|
def OnDroneGroupAdd(self, event):
|
|
event.Skip()
|
|
sizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
label = wx.StaticText()
|
|
self.droneSizer.Add(sizer, 0, wx.EXPAND | wx.LEFT, self.indent)
|
|
|
|
def refreshStatus(self):
|
|
for map in (self.rbLabelMap, self.rbCheckboxMap):
|
|
for rb, items in map.items():
|
|
for item in items:
|
|
item.Enable(rb.GetValue())
|
|
|
|
def rbSelected(self, event):
|
|
event.Skip()
|
|
self.refreshStatus()
|