Use ammo service to generate ammo switcher context menu
This commit is contained in:
@@ -3,19 +3,17 @@ import wx
|
||||
|
||||
import gui.fitCommands as cmd
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.contextMenu import ContextMenuCombined
|
||||
from gui.fitCommands.helpers import getSimilarModPositions
|
||||
from service.ammo import Ammo
|
||||
from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class ChangeModuleAmmo(ContextMenuCombined):
|
||||
|
||||
DAMAGE_TYPES = ("em", "explosive", "kinetic", "thermal")
|
||||
MISSILE_ORDER = ("em", "thermal", "kinetic", "explosive", "mixed")
|
||||
DAMAGE_TYPES = ('em', 'explosive', 'kinetic', 'thermal')
|
||||
MISSILE_ORDER = ('em', 'thermal', 'kinetic', 'explosive', 'mixed')
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
@@ -23,13 +21,13 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
self.loadableCharges = {}
|
||||
|
||||
def display(self, callingWindow, srcContext, mainItem, selection):
|
||||
if srcContext not in ("fittingModule", "projectedModule"):
|
||||
if srcContext not in ('fittingModule', 'projectedModule'):
|
||||
return False
|
||||
|
||||
if self.mainFrame.getActiveFit() is None:
|
||||
return False
|
||||
|
||||
self.mainCharges = self.getChargesForMod(mainItem)
|
||||
self.mainCharges = Ammo.getInstance().getModuleFlatAmmo(mainItem)
|
||||
if not self.mainCharges:
|
||||
return False
|
||||
|
||||
@@ -39,186 +37,79 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return "Charge"
|
||||
|
||||
def getChargesForMod(self, mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
return set()
|
||||
typeID = mod.item.ID
|
||||
if typeID in self.loadableCharges:
|
||||
return self.loadableCharges[typeID]
|
||||
chargeSet = self.loadableCharges.setdefault(typeID, set())
|
||||
# Do not try to grab it for modes which can also be passed as part of selection
|
||||
if isinstance(mod, Module):
|
||||
for charge in mod.getValidCharges():
|
||||
if sMkt.getPublicityByItem(charge):
|
||||
chargeSet.add(charge)
|
||||
return chargeSet
|
||||
|
||||
def turretSorter(self, charge):
|
||||
damage = 0
|
||||
range_ = (self.module.item.getAttribute("maxRange")) * \
|
||||
(charge.getAttribute("weaponRangeMultiplier") or 1)
|
||||
falloff = (self.module.item.getAttribute("falloff") or 0) * \
|
||||
(charge.getAttribute("fallofMultiplier") or 1)
|
||||
for type_ in self.DAMAGE_TYPES:
|
||||
d = charge.getAttribute("%sDamage" % type_)
|
||||
if d > 0:
|
||||
damage += d
|
||||
|
||||
# Take optimal and falloff as range factor
|
||||
rangeFactor = range_ + falloff
|
||||
|
||||
return - rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
def missileSorter(self, charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = self.damageInfo(charge)
|
||||
# Find its position in sort list
|
||||
position = self.MISSILE_ORDER.index(chargeDamageType)
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
def damageInfo(self, charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
damageMap = {}
|
||||
totalDamage = 0
|
||||
# Fill them with the data about charge
|
||||
for damageType in self.DAMAGE_TYPES:
|
||||
currentDamage = charge.getAttribute("{0}Damage".format(damageType)) or 0
|
||||
damageMap[damageType] = currentDamage
|
||||
totalDamage += currentDamage
|
||||
# Detect type of ammo
|
||||
chargeDamageType = None
|
||||
for damageType in damageMap:
|
||||
# If all damage belongs to certain type purely, set appropriate
|
||||
# ammoType
|
||||
if damageMap[damageType] == totalDamage:
|
||||
chargeDamageType = damageType
|
||||
break
|
||||
# Else consider ammo as mixed damage
|
||||
if chargeDamageType is None:
|
||||
chargeDamageType = "mixed"
|
||||
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
@staticmethod
|
||||
def numericConverter(string):
|
||||
return int(string) if string.isdigit() else string
|
||||
|
||||
def nameSorter(self, charge):
|
||||
parts = charge.name.split(" ")
|
||||
return list(map(self.numericConverter, parts))
|
||||
return 'Charge'
|
||||
|
||||
def addCharge(self, menu, charge):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
name = charge.name if charge is not None else "Empty"
|
||||
self.chargeIds[id_] = charge
|
||||
name = charge.name if charge is not None else 'Empty'
|
||||
self.chargeEventMap[id_] = charge
|
||||
item = wx.MenuItem(menu, id_, name)
|
||||
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch, item)
|
||||
item.charge = charge
|
||||
if charge is not None and charge.iconID is not None:
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, "icons")
|
||||
bitmap = BitmapLoader.getBitmap(charge.iconID, 'icons')
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def addSeperator(m, text):
|
||||
def addSeparator(m, text):
|
||||
id_ = ContextMenuCombined.nextID()
|
||||
m.Append(id_, '─ %s ─' % text)
|
||||
m.Enable(id_, False)
|
||||
|
||||
def getSubMenu(self, callingWindow, context, mainItem, selection, rootMenu, i, pitem):
|
||||
msw = True if "wxMSW" in wx.PlatformInfo else False
|
||||
m = wx.Menu()
|
||||
self.chargeIds = {}
|
||||
hardpoint = self.module.hardpoint
|
||||
moduleName = self.module.item.name
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if hardpoint == FittingHardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None:
|
||||
self.addSeperator(m, "Long Range")
|
||||
items = []
|
||||
range_ = None
|
||||
nameBase = None
|
||||
sub = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.turretSorter)
|
||||
for charge in chargesSorted:
|
||||
if "civilian" in charge.name.lower():
|
||||
continue
|
||||
currBase = charge.name.rsplit()[-2:]
|
||||
currRange = charge.getAttribute("weaponRangeMultiplier")
|
||||
if nameBase is None or range_ != currRange or nameBase != currBase:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
sub = None
|
||||
base = charge
|
||||
nameBase = currBase
|
||||
range_ = currRange
|
||||
item = self.addCharge(rootMenu if msw else m, charge)
|
||||
items.append(item)
|
||||
msw = True if 'wxMSW' in wx.PlatformInfo else False
|
||||
menu = wx.Menu()
|
||||
self.chargeEventMap = {}
|
||||
modType, chargeDict = Ammo.getInstance().getModuleStructuredAmmo(self.module)
|
||||
if modType == 'ddTurret':
|
||||
self.addSeparator(menu, 'Long Range')
|
||||
menuItems = []
|
||||
for charges in chargeDict.values():
|
||||
if len(charges) == 1:
|
||||
menuItems.append(self.addCharge(rootMenu if msw else menu, charges[0]))
|
||||
else:
|
||||
if sub is None and item and base:
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, base))
|
||||
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
for item in items:
|
||||
m.Append(item)
|
||||
|
||||
self.addSeperator(m, "Short Range")
|
||||
elif hardpoint == FittingHardpoint.MISSILE and moduleName != 'Festival Launcher':
|
||||
type_ = None
|
||||
sub = None
|
||||
defender = None
|
||||
chargesSorted = sorted(self.mainCharges, key=self.missileSorter)
|
||||
for charge in chargesSorted:
|
||||
currType = self.damageInfo(charge)[0]
|
||||
|
||||
if currType != type_ or type_ is None:
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
|
||||
type_ = currType
|
||||
item = wx.MenuItem(m, wx.ID_ANY, type_.capitalize())
|
||||
bitmap = BitmapLoader.getBitmap("%s_small" % type_, "gui")
|
||||
if bitmap is not None:
|
||||
item.SetBitmap(bitmap)
|
||||
|
||||
sub = wx.Menu()
|
||||
sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
self.addSeperator(sub, "Less Damage")
|
||||
item.SetSubMenu(sub)
|
||||
m.Append(item)
|
||||
|
||||
if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"):
|
||||
sub.Append(self.addCharge(rootMenu if msw else sub, charge))
|
||||
else:
|
||||
defender = charge
|
||||
|
||||
if defender is not None:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, defender))
|
||||
if sub is not None:
|
||||
self.addSeperator(sub, "More Damage")
|
||||
else:
|
||||
chargesSorted = sorted(self.mainCharges, key=self.nameSorter)
|
||||
for charge in chargesSorted:
|
||||
m.Append(self.addCharge(rootMenu if msw else m, charge))
|
||||
|
||||
m.Append(self.addCharge(rootMenu if msw else m, None))
|
||||
return m
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
baseCharge = charges[0]
|
||||
menuItem = self.addCharge(rootMenu if msw else menu, baseCharge)
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self.addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self.addCharge(rootMenu if msw else subMenu, charge))
|
||||
self.addSeparator(subMenu, 'More Damage')
|
||||
for menuItem in menuItems:
|
||||
menu.Append(menuItem)
|
||||
self.addSeparator(menu, 'Short Range')
|
||||
elif modType == 'ddMissile':
|
||||
menuItems = []
|
||||
for chargeCatName, charges in chargeDict.items():
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem = wx.MenuItem(menu, wx.ID_ANY, chargeCatName.capitalize())
|
||||
menuItems.append(menuItem)
|
||||
subMenu = wx.Menu()
|
||||
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
|
||||
menuItem.SetSubMenu(subMenu)
|
||||
self.addSeparator(subMenu, 'Less Damage')
|
||||
for charge in charges:
|
||||
subMenu.Append(self.addCharge(rootMenu if msw else subMenu, charge))
|
||||
self.addSeparator(subMenu, 'More Damage')
|
||||
for menuItem in menuItems:
|
||||
menu.Append(menuItem)
|
||||
elif modType == 'general':
|
||||
for charge in chargeDict['general']:
|
||||
menu.Append(self.addCharge(rootMenu if msw else menu, charge))
|
||||
menu.Append(self.addCharge(rootMenu if msw else menu, None))
|
||||
return menu
|
||||
|
||||
def handleAmmoSwitch(self, event):
|
||||
charge = self.chargeIds.get(event.Id, False)
|
||||
charge = self.chargeEventMap.get(event.Id, False)
|
||||
if charge is False:
|
||||
event.Skip()
|
||||
return
|
||||
@@ -254,7 +145,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
positions = []
|
||||
for position, mod in enumerate(modContainer):
|
||||
if mod in self.selection:
|
||||
modCharges = self.getChargesForMod(mod)
|
||||
modCharges = Ammo.getInstance().getModuleFlatAmmo(mod)
|
||||
if modCharges.issubset(self.mainCharges):
|
||||
positions.append(position)
|
||||
self.mainFrame.command.Submit(command(
|
||||
|
||||
153
service/ammo.py
153
service/ammo.py
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
import math
|
||||
from collections import OrderedDict
|
||||
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
@@ -52,95 +53,99 @@ class Ammo:
|
||||
|
||||
@classmethod
|
||||
def getModuleStructuredAmmo(cls, mod):
|
||||
|
||||
def getChargeDamageInfo(charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
damageMap = {}
|
||||
totalDamage = 0
|
||||
# Fill them with the data about charge
|
||||
for damageType in DmgTypes.names():
|
||||
currentDamage = charge.getAttribute('{}Damage'.format(damageType)) or 0
|
||||
damageMap[damageType] = currentDamage
|
||||
totalDamage += currentDamage
|
||||
# Detect type of ammo
|
||||
chargeDamageType = None
|
||||
for damageType in damageMap:
|
||||
# If all damage belongs to certain type purely, set appropriate
|
||||
# ammoType
|
||||
if damageMap[damageType] == totalDamage:
|
||||
chargeDamageType = damageType
|
||||
break
|
||||
# Else consider ammo as mixed damage
|
||||
if chargeDamageType is None:
|
||||
chargeDamageType = 'mixed'
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
def turretSorter(mod, charge):
|
||||
damage = 0
|
||||
range_ = (mod.item.getAttribute('maxRange')) * \
|
||||
(charge.getAttribute('weaponRangeMultiplier') or 1)
|
||||
falloff = (mod.item.getAttribute('falloff') or 0) * \
|
||||
(charge.getAttribute('fallofMultiplier') or 1)
|
||||
for type_ in DmgTypes.names():
|
||||
d = charge.getAttribute('%sDamage' % type_)
|
||||
if d > 0:
|
||||
damage += d
|
||||
# Take optimal and falloff as range factor
|
||||
rangeFactor = range_ + falloff
|
||||
return -rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
def missileSorter(mod, charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = getChargeDamageInfo(charge)
|
||||
# Find its position in sort list
|
||||
try:
|
||||
position = DmgTypes.names().index(chargeDamageType)
|
||||
# Put charges which have non-standard damage type after charges with
|
||||
# standard damage type
|
||||
except ValueError:
|
||||
position = math.inf
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
def nameSorter(charge):
|
||||
parts = charge.name.split(" ")
|
||||
return [int(p) if p.isdigit() else p for p in parts]
|
||||
|
||||
chargesFlat = cls.getModuleFlatAmmo(mod)
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if mod.hardpoint == FittingHardpoint.TURRET and mod.getModifiedItemAttr('miningAmount', None) is None:
|
||||
all = []
|
||||
|
||||
def turretSorter(charge):
|
||||
damage = 0
|
||||
range_ = (mod.item.getAttribute('maxRange')) * \
|
||||
(charge.getAttribute('weaponRangeMultiplier') or 1)
|
||||
falloff = (mod.item.getAttribute('falloff') or 0) * \
|
||||
(charge.getAttribute('fallofMultiplier') or 1)
|
||||
for type_ in DmgTypes.names():
|
||||
d = charge.getAttribute('%sDamage' % type_)
|
||||
if d > 0:
|
||||
damage += d
|
||||
# Take optimal and falloff as range factor
|
||||
rangeFactor = range_ + falloff
|
||||
return -rangeFactor, charge.name.rsplit()[-2:], damage, charge.name
|
||||
|
||||
all = OrderedDict()
|
||||
sub = []
|
||||
prevNameBase = None
|
||||
prevRange = None
|
||||
for charge in sorted(chargesFlat, key=turretSorter):
|
||||
if 'civilian' in charge.name.lower():
|
||||
continue
|
||||
currNameBase = charge.name.rsplit()[-2:]
|
||||
currNameBase = ' '.join(charge.name.rsplit()[-2:])
|
||||
currRange = charge.getAttribute('weaponRangeMultiplier')
|
||||
if prevNameBase is None or currRange != prevRange or currNameBase != prevNameBase:
|
||||
if sub:
|
||||
all.append(sub)
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevNameBase = currNameBase
|
||||
prevRange = currRange
|
||||
else:
|
||||
sub.append(charge)
|
||||
if sub and (currRange != prevRange or currNameBase != prevNameBase):
|
||||
all[prevNameBase] = sub
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevNameBase = currNameBase
|
||||
prevRange = currRange
|
||||
else:
|
||||
if sub:
|
||||
all[prevNameBase] = sub
|
||||
return 'ddTurret', all
|
||||
|
||||
elif mod.hardpoint == FittingHardpoint.MISSILE and mod.item.name != 'Festival Launcher':
|
||||
all = []
|
||||
|
||||
def getChargeDamageInfo(charge):
|
||||
# Set up data storage for missile damage stuff
|
||||
damageMap = {}
|
||||
totalDamage = 0
|
||||
# Fill them with the data about charge
|
||||
for damageType in DmgTypes.names():
|
||||
currentDamage = charge.getAttribute('{}Damage'.format(damageType)) or 0
|
||||
damageMap[damageType] = currentDamage
|
||||
totalDamage += currentDamage
|
||||
# Detect type of ammo
|
||||
chargeDamageType = None
|
||||
for damageType in damageMap:
|
||||
# If all damage belongs to certain type purely, set appropriate
|
||||
# ammoType
|
||||
if damageMap[damageType] == totalDamage:
|
||||
chargeDamageType = damageType
|
||||
break
|
||||
# Else consider ammo as mixed damage
|
||||
if chargeDamageType is None:
|
||||
chargeDamageType = 'mixed'
|
||||
return chargeDamageType, totalDamage
|
||||
|
||||
def missileSorter(charge):
|
||||
# Get charge damage type and total damage
|
||||
chargeDamageType, totalDamage = getChargeDamageInfo(charge)
|
||||
# Find its position in sort list
|
||||
try:
|
||||
position = DmgTypes.names().index(chargeDamageType)
|
||||
# Put charges which have non-standard damage type after charges with
|
||||
# standard damage type
|
||||
except ValueError:
|
||||
position = math.inf
|
||||
return position, totalDamage, charge.name
|
||||
|
||||
all = OrderedDict()
|
||||
sub = []
|
||||
prevType = None
|
||||
for charge in sorted(chargesFlat, key=missileSorter):
|
||||
currType = getChargeDamageInfo(charge)[0]
|
||||
if prevType is None or currType != prevType:
|
||||
if sub:
|
||||
all.append(sub)
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevType = currType
|
||||
else:
|
||||
sub.append(charge)
|
||||
if sub and currType != prevType:
|
||||
all[prevType] = sub
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevType = currType
|
||||
else:
|
||||
if sub:
|
||||
all[prevType] = sub
|
||||
return 'ddMissile', all
|
||||
|
||||
else:
|
||||
return 'general', sorted(chargesFlat, key=nameSorter)
|
||||
|
||||
def nameSorter(charge):
|
||||
parts = charge.name.split(" ")
|
||||
return [int(p) if p.isdigit() else p for p in parts]
|
||||
|
||||
return 'general', {'general': sorted(chargesFlat, key=nameSorter)}
|
||||
|
||||
Reference in New Issue
Block a user