Merge branch 'ammo_graph' into singularity
# Conflicts: # gui/targetProfileEditor.py
This commit is contained in:
@@ -26,6 +26,8 @@ from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, re
|
||||
|
||||
class BaseEffect:
|
||||
|
||||
dealsDamage = False
|
||||
|
||||
@staticmethod
|
||||
def handler(fit, module, context, projectionRange, **kwargs):
|
||||
pass
|
||||
@@ -62,6 +64,7 @@ class Effect10(BaseEffect):
|
||||
Modules from group: Energy Weapon (212 of 214)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -169,6 +172,7 @@ class Effect34(BaseEffect):
|
||||
Modules from group: Projectile Weapon (165 of 165)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -189,6 +193,7 @@ class Effect38(BaseEffect):
|
||||
Modules from group: Smart Bomb (118 of 118)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
|
||||
@@ -555,6 +560,7 @@ class Effect101(BaseEffect):
|
||||
Structure Modules named like: Standup Launcher (7 of 7)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active', 'projected'
|
||||
|
||||
@staticmethod
|
||||
@@ -15124,6 +15130,7 @@ class Effect4489(BaseEffect):
|
||||
Module: 'Judgment' Electromagnetic Doomsday
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -15140,6 +15147,7 @@ class Effect4490(BaseEffect):
|
||||
Module: 'Oblivion' Kinetic Doomsday
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -15156,6 +15164,7 @@ class Effect4491(BaseEffect):
|
||||
Module: 'Aurora Ominae' Thermal Doomsday
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -15172,6 +15181,7 @@ class Effect4492(BaseEffect):
|
||||
Module: 'Gjallarhorn' Explosive Doomsday
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -27364,6 +27374,7 @@ class Effect6431(BaseEffect):
|
||||
Fighters from group: Light Fighter (32 of 32)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
displayName = 'Missile Attack'
|
||||
hasCharges = True
|
||||
prefix = 'fighterAbilityMissiles'
|
||||
@@ -27644,6 +27655,7 @@ class Effect6465(BaseEffect):
|
||||
Fighters from group: Heavy Fighter (34 of 34)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
displayName = 'Turret Attack'
|
||||
prefix = 'fighterAbilityAttackMissile'
|
||||
type = 'active'
|
||||
@@ -27686,6 +27698,7 @@ class Effect6472(BaseEffect):
|
||||
Modules named like: Lance (4 of 4)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -27702,6 +27715,7 @@ class Effect6473(BaseEffect):
|
||||
Module: Bosonic Field Generator
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
@@ -27912,6 +27926,7 @@ class Effect6485(BaseEffect):
|
||||
Fighters from group: Heavy Fighter (16 of 34)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
displayName = 'Bomb'
|
||||
hasCharges = True
|
||||
prefix = 'fighterAbilityLaunchBomb'
|
||||
@@ -33964,6 +33979,7 @@ class Effect6995(BaseEffect):
|
||||
Modules from group: Precursor Weapon (19 of 19)
|
||||
"""
|
||||
|
||||
dealsDamage = True
|
||||
type = 'active'
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -146,6 +146,12 @@ class Effect(EqBase):
|
||||
|
||||
return self.__effectDef is not None
|
||||
|
||||
@property
|
||||
def dealsDamage(self):
|
||||
if not self.__generated:
|
||||
self.__generateHandler()
|
||||
return self.__dealsDamage
|
||||
|
||||
def isType(self, type):
|
||||
"""
|
||||
Check if this effect is of the passed type
|
||||
@@ -167,6 +173,7 @@ class Effect(EqBase):
|
||||
self.__handler = getattr(effectDef, "handler", eos.effects.BaseEffect.handler)
|
||||
self.__runTime = getattr(effectDef, "runTime", "normal")
|
||||
self.__activeByDefault = getattr(effectDef, "activeByDefault", True)
|
||||
self.__dealsDamage = effectDef.dealsDamage
|
||||
effectType = getattr(effectDef, "type", None)
|
||||
effectType = effectType if isinstance(effectType, tuple) or effectType is None else (effectType,)
|
||||
self.__type = effectType
|
||||
@@ -175,6 +182,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.debug("ImportError generating handler: {0}", e)
|
||||
except AttributeError as e:
|
||||
@@ -182,6 +190,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.error("AttributeError generating handler: {0}", e)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
@@ -190,6 +199,7 @@ class Effect(EqBase):
|
||||
self.__handler = eos.effects.DummyEffect.handler
|
||||
self.__runTime = "normal"
|
||||
self.__activeByDefault = True
|
||||
self.__dealsDamage = False
|
||||
self.__type = None
|
||||
pyfalog.critical("Exception generating handler:")
|
||||
pyfalog.critical(e)
|
||||
|
||||
@@ -364,3 +364,11 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.item.groupID in fitDroneGroupLimits:
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (ignoreState or self.amountActive > 0):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -441,3 +441,15 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def canDealDamage(self, ignoreState=False, ignoreAbilityState=False):
|
||||
if self.item is None:
|
||||
return False
|
||||
if not self.active and not ignoreState:
|
||||
return False
|
||||
for ability in self.abilities:
|
||||
if not ability.active and not ignoreAbilityState:
|
||||
continue
|
||||
if ability.effect.dealsDamage:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -461,6 +461,20 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDealDamage(self, ignoreState=False):
|
||||
if self.isEmpty:
|
||||
return False
|
||||
for effect in self.item.effects.values():
|
||||
if effect.dealsDamage and (
|
||||
ignoreState or
|
||||
effect.isType('offline') or
|
||||
(effect.isType('passive') and self.state >= FittingModuleState.ONLINE) or
|
||||
(effect.isType('active') and self.state >= FittingModuleState.ACTIVE) or
|
||||
(effect.isType('overheat') and self.state >= FittingModuleState.OVERHEATED)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
|
||||
return {0: DmgTypes(0, 0, 0, 0)}
|
||||
|
||||
@@ -27,7 +27,7 @@ import gui.globalEvents as GE
|
||||
import gui.mainFrame
|
||||
from graphs.data.base import FitGraph
|
||||
from graphs.events import RESIST_MODE_CHANGED
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from service.settings import GraphSettings
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import wx
|
||||
|
||||
|
||||
class AuxiliaryFrame(wx.Frame):
|
||||
class AuxiliaryMixin:
|
||||
|
||||
_instance = None
|
||||
|
||||
@@ -55,14 +55,26 @@ class AuxiliaryFrame(wx.Frame):
|
||||
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
|
||||
|
||||
@classmethod
|
||||
def openOne(cls, parent, *args, **kwargs):
|
||||
def openOne(cls, parent, *args, forceReopen=False, **kwargs):
|
||||
"""If window is open and alive - raise it, open otherwise"""
|
||||
if not cls._instance:
|
||||
if not cls._instance or forceReopen:
|
||||
if cls._instance:
|
||||
cls._instance.Close()
|
||||
frame = cls(parent, *args, **kwargs)
|
||||
cls._instance = frame
|
||||
frame.Show()
|
||||
else:
|
||||
cls._instance.Raise()
|
||||
return cls._instance
|
||||
|
||||
|
||||
def OnSuppressedAction(self, event):
|
||||
return
|
||||
|
||||
|
||||
class AuxiliaryFrame(AuxiliaryMixin, wx.Frame):
|
||||
pass
|
||||
|
||||
|
||||
class AuxiliaryDialog(AuxiliaryMixin, wx.Dialog):
|
||||
pass
|
||||
@@ -36,6 +36,11 @@ import gui.fitCommands as cmd
|
||||
from gui.fitCommands.helpers import droneStackLimit
|
||||
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
|
||||
class DroneViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(DroneViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -186,17 +191,13 @@ class DroneView(Display):
|
||||
self.mainFrame.command.Submit(cmd.GuiMergeLocalDroneStacksCommand(
|
||||
fitID=fitID, srcPosition=srcPosition, dstPosition=dstPosition))
|
||||
|
||||
DRONE_ORDER = ('Light Scout Drones', 'Medium Scout Drones',
|
||||
'Heavy Attack Drones', 'Sentry Drones', 'Combat Utility Drones',
|
||||
'Electronic Warfare Drones', 'Logistic Drones', 'Mining Drones', 'Salvage Drones')
|
||||
|
||||
def droneKey(self, drone):
|
||||
@staticmethod
|
||||
def droneKey(drone):
|
||||
sMkt = Market.getInstance()
|
||||
|
||||
groupName = sMkt.getMarketGroupByItem(drone.item).name
|
||||
|
||||
return (self.DRONE_ORDER.index(groupName),
|
||||
drone.item.name)
|
||||
return (DRONE_ORDER.index(groupName), drone.item.name)
|
||||
|
||||
def fitChanged(self, event):
|
||||
event.Skip()
|
||||
|
||||
@@ -34,6 +34,9 @@ from service.fit import Fit
|
||||
from service.market import Market
|
||||
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
|
||||
class FighterViewDrop(wx.DropTarget):
|
||||
def __init__(self, dropFn, *args, **kwargs):
|
||||
super(FighterViewDrop, self).__init__(*args, **kwargs)
|
||||
@@ -250,11 +253,10 @@ class FighterDisplay(d.Display):
|
||||
def _merge(src, dst):
|
||||
return
|
||||
|
||||
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
|
||||
|
||||
def fighterKey(self, fighter):
|
||||
@staticmethod
|
||||
def fighterKey(fighter):
|
||||
groupName = Market.getInstance().getGroupByItem(fighter.item).name
|
||||
orderPos = self.FIGHTER_ORDER.index(groupName)
|
||||
orderPos = FIGHTER_ORDER.index(groupName)
|
||||
# Sort support fighters by name, ignore their abilities
|
||||
if groupName == 'Support Fighter':
|
||||
abilityEffectIDs = ()
|
||||
|
||||
@@ -6,6 +6,7 @@ from gui.builtinContextMenus import fitAddCurrentlyOpen
|
||||
from gui.builtinContextMenus import envEffectAdd
|
||||
from gui.builtinContextMenus import commandFitAdd
|
||||
from gui.builtinContextMenus.targetProfile import adder
|
||||
from gui.builtinContextMenus import graphFitAmmoPicker
|
||||
# Often-used item manipulations
|
||||
from gui.builtinContextMenus import shipModeChange
|
||||
from gui.builtinContextMenus import moduleAmmoChange
|
||||
|
||||
241
gui/builtinContextMenus/graphFitAmmoPicker.py
Normal file
241
gui/builtinContextMenus/graphFitAmmoPicker.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# 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
|
||||
|
||||
|
||||
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 '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()
|
||||
@@ -3,33 +3,28 @@ 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")
|
||||
|
||||
def __init__(self):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
# Format: {type ID: set(loadable, charges)}
|
||||
self.loadableCharges = {}
|
||||
self.loadableChargesCache = {}
|
||||
|
||||
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 = self._getAmmo(mainItem)
|
||||
if not self.mainCharges:
|
||||
return False
|
||||
|
||||
@@ -39,186 +34,81 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
return True
|
||||
|
||||
def getText(self, callingWindow, itmContext, mainItem, selection):
|
||||
return "Charge"
|
||||
return 'Charge'
|
||||
|
||||
def getChargesForMod(self, mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
def _getAmmo(self, mod):
|
||||
if mod.itemID is None:
|
||||
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
|
||||
if mod.itemID not in self.loadableChargesCache:
|
||||
self.loadableChargesCache[mod.itemID] = Ammo.getInstance().getModuleFlatAmmo(mod)
|
||||
return self.loadableChargesCache[mod.itemID]
|
||||
|
||||
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))
|
||||
|
||||
def addCharge(self, menu, 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, ammo=self.mainCharges)
|
||||
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
|
||||
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():
|
||||
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 +144,7 @@ class ChangeModuleAmmo(ContextMenuCombined):
|
||||
positions = []
|
||||
for position, mod in enumerate(modContainer):
|
||||
if mod in self.selection:
|
||||
modCharges = self.getChargesForMod(mod)
|
||||
modCharges = self._getAmmo(mod)
|
||||
if modCharges.issubset(self.mainCharges):
|
||||
positions.append(position)
|
||||
self.mainFrame.command.Submit(command(
|
||||
|
||||
@@ -35,7 +35,7 @@ class ItemDescription(wx.Panel):
|
||||
self.Layout()
|
||||
|
||||
self.description.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.description.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.description.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
self.popupMenu = wx.Menu()
|
||||
copyItem = wx.MenuItem(self.popupMenu, 1, 'Copy')
|
||||
@@ -50,7 +50,7 @@ class ItemDescription(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -14,7 +14,7 @@ class ItemTraits(wx.Panel):
|
||||
self.traits.SetPage(item.traits.traitText)
|
||||
|
||||
self.traits.Bind(wx.EVT_CONTEXT_MENU, self.onPopupMenu)
|
||||
self.traits.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
|
||||
self.traits.Bind(wx.EVT_KEY_UP, self.onKeyUp)
|
||||
|
||||
mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0)
|
||||
self.Layout()
|
||||
@@ -32,7 +32,7 @@ class ItemTraits(wx.Panel):
|
||||
if selectedMenuItem == 1: # Copy was chosen
|
||||
self.copySelectionToClipboard()
|
||||
|
||||
def onKeyDown(self, event):
|
||||
def onKeyUp(self, event):
|
||||
keyCode = event.GetKeyCode()
|
||||
# Ctrl + C
|
||||
if keyCode == 67 and event.ControlDown():
|
||||
|
||||
@@ -203,22 +203,6 @@ class ItemView(Display):
|
||||
self.setToggles()
|
||||
self.filterItemStore()
|
||||
|
||||
def itemSort(self, item):
|
||||
sMkt = self.sMkt
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
def contextMenu(self, event):
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
self.ensureSelection(clickedPos)
|
||||
@@ -241,7 +225,7 @@ class ItemView(Display):
|
||||
self.unselectAll()
|
||||
# Perform sorting, using item's meta levels besides other stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
# Mark current item list as active
|
||||
self.active = items
|
||||
# Show them
|
||||
@@ -251,12 +235,10 @@ class ItemView(Display):
|
||||
if len(items) > 1:
|
||||
# Re-sort stuff
|
||||
if self.marketBrowser.mode != 'recent':
|
||||
items.sort(key=self.itemSort)
|
||||
|
||||
items.sort(key=self.sMkt.itemSort)
|
||||
for i, item in enumerate(items[:9]):
|
||||
# set shortcut info for first 9 modules
|
||||
item.marketShortcut = i + 1
|
||||
|
||||
Display.refresh(self, items)
|
||||
|
||||
def columnBackground(self, colItem, item):
|
||||
|
||||
@@ -86,7 +86,7 @@ class PFSearchBox(wx.Window):
|
||||
|
||||
def OnKeyPress(self, event):
|
||||
if event.RawControlDown() and event.GetKeyCode() == wx.WXK_BACK:
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
HandleCtrlBackspace(self.EditBox)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ class FittingView(d.Display):
|
||||
self.hoveredRow = None
|
||||
self.hoveredColumn = None
|
||||
|
||||
self.Bind(wx.EVT_KEY_DOWN, self.kbEvent)
|
||||
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_RIGHT_DOWN, self.click)
|
||||
self.Bind(wx.EVT_MIDDLE_DOWN, self.click)
|
||||
|
||||
@@ -34,7 +34,7 @@ from wx.lib.agw.floatspin import FloatSpin
|
||||
|
||||
import config
|
||||
import gui.globalEvents as GE
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor, TextEntryValidatedDialog
|
||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||
|
||||
@@ -26,7 +26,7 @@ import wx
|
||||
from logbook import Logger
|
||||
|
||||
import eos.db
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.builtinShipBrowser.events import FitSelected
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import wx
|
||||
from logbook import Logger
|
||||
|
||||
import config
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from service.prereqsCheck import version_block
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import config
|
||||
import gui.globalEvents as GE
|
||||
from eos.db import getItem
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.display import Display
|
||||
from gui.characterEditor import APIView
|
||||
from service.character import Character
|
||||
|
||||
@@ -23,7 +23,7 @@ import wx
|
||||
import config
|
||||
import gui.mainFrame
|
||||
from eos.saveddata.module import Module
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy
|
||||
from gui.builtinItemStatsViews.itemAttributes import ItemParams
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
|
||||
@@ -10,7 +10,7 @@ import gui.builtinMarketBrowser.pfSearchBox as SBox
|
||||
import gui.display as d
|
||||
import gui.globalEvents as GE
|
||||
from eos.db.gamedata.queries import getAttributeInfo, getItem
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.marketBrowser import SearchBox
|
||||
from service.fit import Fit
|
||||
@@ -213,19 +213,7 @@ class ItemView(d.Display):
|
||||
def itemSort(self, item):
|
||||
sMkt = Market.getInstance()
|
||||
isFittable = item.group.name in sMkt.FIT_GROUPS or item.category.name in sMkt.FIT_CATEGORIES
|
||||
catname = sMkt.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = sMkt.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
parentname = sMkt.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = sMkt.getMetaGroupIdByItem(item)
|
||||
metatab = sMkt.META_MAP_REVERSE_INDICES.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
|
||||
return not isFittable, catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
return (not isFittable, *sMkt.itemSort(item))
|
||||
|
||||
def populateSearch(self, itemIDs):
|
||||
items = Market.getItems(itemIDs)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
from gui.auxFrame import AuxiliaryFrame
|
||||
from gui.auxWindow import AuxiliaryFrame
|
||||
from gui.builtinViews.entityEditor import BaseValidator, EntityEditor
|
||||
from gui.builtinViews.implantEditor import BaseImplantEditorView
|
||||
from gui.utils.clipboard import fromClipboard, toClipboard
|
||||
|
||||
2
pyfa.py
2
pyfa.py
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# ==============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
|
||||
151
service/ammo.py
Normal file
151
service/ammo.py
Normal file
@@ -0,0 +1,151 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
from collections import OrderedDict
|
||||
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.saveddata.module import Module
|
||||
from eos.utils.stats import DmgTypes
|
||||
from service.market import Market
|
||||
|
||||
|
||||
class Ammo:
|
||||
|
||||
instance = None
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
if cls.instance is None:
|
||||
cls.instance = Ammo()
|
||||
|
||||
return cls.instance
|
||||
|
||||
@staticmethod
|
||||
def getModuleFlatAmmo(mod):
|
||||
sMkt = Market.getInstance()
|
||||
if mod is None or mod.isEmpty:
|
||||
return set()
|
||||
chargeSet = set()
|
||||
# Do not try to grab it for t3d 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
|
||||
|
||||
@classmethod
|
||||
def getModuleStructuredAmmo(cls, mod, ammo=None):
|
||||
chargesFlat = cls.getModuleFlatAmmo(mod) if ammo is None else ammo
|
||||
# Make sure we do not consider mining turrets as combat turrets
|
||||
if mod.hardpoint == FittingHardpoint.TURRET and mod.getModifiedItemAttr('miningAmount', None) is None:
|
||||
|
||||
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 = ' '.join(charge.name.rsplit()[-2:])
|
||||
currRange = charge.getAttribute('weaponRangeMultiplier')
|
||||
if sub and (currRange != prevRange or currNameBase != prevNameBase):
|
||||
all[sub[0].name] = sub
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevNameBase = currNameBase
|
||||
prevRange = currRange
|
||||
else:
|
||||
if sub:
|
||||
all[sub[0].name] = sub
|
||||
return 'ddTurret', all
|
||||
|
||||
elif mod.hardpoint == FittingHardpoint.MISSILE and mod.item.name != 'Festival Launcher':
|
||||
|
||||
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 sub and currType != prevType:
|
||||
all[prevType] = sub
|
||||
sub = []
|
||||
sub.append(charge)
|
||||
prevType = currType
|
||||
else:
|
||||
if sub:
|
||||
all[prevType] = sub
|
||||
return 'ddMissile', all
|
||||
|
||||
else:
|
||||
|
||||
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)}
|
||||
@@ -407,6 +407,12 @@ class Market:
|
||||
self.META_MAP["normal"] = frozenset((0, *(mg.ID for mg in eos.db.getMetaGroups() if mg.ID not in nonNormalMetas)))
|
||||
self.META_MAP.move_to_end("normal", last=False)
|
||||
self.META_MAP_REVERSE = {sv: k for k, v in self.META_MAP.items() for sv in v}
|
||||
self.META_MAP_REVERSE_GROUPED = {}
|
||||
i = 0
|
||||
for mgids in self.META_MAP.values():
|
||||
for mgid in mgids:
|
||||
self.META_MAP_REVERSE_GROUPED[mgid] = i
|
||||
i += 1
|
||||
self.META_MAP_REVERSE_INDICES = self.__makeReverseMetaMapIndices()
|
||||
self.SEARCH_CATEGORIES = (
|
||||
"Drone",
|
||||
@@ -939,3 +945,19 @@ class Market:
|
||||
while len(recentlyUsedModules) >= 20:
|
||||
recentlyUsedModules.pop(-1)
|
||||
recentlyUsedModules.insert(0, itemID)
|
||||
|
||||
def itemSort(self, item, reverseMktGrp=False):
|
||||
catname = self.getCategoryByItem(item).name
|
||||
try:
|
||||
mktgrpid = self.getMarketGroupByItem(item).ID
|
||||
except AttributeError:
|
||||
mktgrpid = -1
|
||||
pyfalog.warning("unable to find market group for {}".format(item.name))
|
||||
if reverseMktGrp:
|
||||
mktgrpid = -mktgrpid
|
||||
parentname = self.getParentItemByItem(item).name
|
||||
# Get position of market group
|
||||
metagrpid = self.getMetaGroupIdByItem(item)
|
||||
metatab = self.META_MAP_REVERSE_GROUPED.get(metagrpid)
|
||||
metalvl = item.metaLevel or 0
|
||||
return catname, mktgrpid, parentname, metatab, metalvl, item.name
|
||||
|
||||
Reference in New Issue
Block a user