Use ammo service to generate ammo switcher context menu

This commit is contained in:
DarkPhoenix
2019-11-13 13:31:40 +03:00
parent a917207e07
commit 9146c0f2c6
2 changed files with 137 additions and 241 deletions

View File

@@ -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(

View File

@@ -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)}