Merge pull request #2069 from Gochim/master
[Updated] Implemented copying the currently open fit stats to the clipboard.
This commit is contained in:
@@ -12,7 +12,7 @@ Virtual environment will be created in *PyfaEnv* folder. Project will be cloned
|
||||
|
||||
## Setting up the project manually
|
||||
|
||||
Clone the repo
|
||||
Clone the repository
|
||||
```
|
||||
git clone <repo> PyfaDEV
|
||||
```
|
||||
@@ -36,7 +36,7 @@ pip install -r PyfaDEV\requirements.txt
|
||||
```
|
||||
> For some Linux distributions, you may need to install separate wxPython bindings, such as `python-matplotlib-wx`
|
||||
|
||||
Check that libs from *requirements.txt* are installed
|
||||
Check that the libs from *requirements.txt* are installed
|
||||
```
|
||||
pip list
|
||||
```
|
||||
|
||||
@@ -78,6 +78,15 @@ class DamagePattern:
|
||||
"exp" : "explosive"
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def oneType(cls, damageType, amount=100):
|
||||
pattern = DamagePattern()
|
||||
pattern.update(amount if damageType == "em" else 0,
|
||||
amount if damageType == "thermal" else 0,
|
||||
amount if damageType == "kinetic" else 0,
|
||||
amount if damageType == "explosive" else 0)
|
||||
return pattern
|
||||
|
||||
@classmethod
|
||||
def importPatterns(cls, text):
|
||||
lines = re.split('[\n\r]+', text)
|
||||
|
||||
@@ -40,6 +40,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
copyFormatEsi = 3
|
||||
copyFormatMultiBuy = 4
|
||||
copyFormatEfs = 5
|
||||
copyFormatFitStats = 6
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
@@ -50,7 +51,8 @@ class CopySelectDialog(wx.Dialog):
|
||||
CopySelectDialog.copyFormatDna : self.exportDna,
|
||||
CopySelectDialog.copyFormatEsi : self.exportEsi,
|
||||
CopySelectDialog.copyFormatMultiBuy: self.exportMultiBuy,
|
||||
CopySelectDialog.copyFormatEfs : self.exportEfs
|
||||
CopySelectDialog.copyFormatEfs : self.exportEfs,
|
||||
CopySelectDialog.copyFormatFitStats: self.exportFitStats
|
||||
}
|
||||
|
||||
self.mainFrame = parent
|
||||
@@ -62,6 +64,7 @@ class CopySelectDialog(wx.Dialog):
|
||||
("ESI", (CopySelectDialog.copyFormatEsi, None)),
|
||||
("DNA", (CopySelectDialog.copyFormatDna, DNA_OPTIONS)),
|
||||
("EFS", (CopySelectDialog.copyFormatEfs, None)),
|
||||
("Fit stats", (CopySelectDialog.copyFormatFitStats, None)),
|
||||
# ("XML", (CopySelectDialog.copyFormatXml, None)),
|
||||
))
|
||||
|
||||
@@ -117,7 +120,8 @@ class CopySelectDialog(wx.Dialog):
|
||||
self.Center()
|
||||
|
||||
def Validate(self):
|
||||
# Since this dialog is shown through aa ShowModal(), we hook into the Validate function to veto the closing of the dialog until we're ready.
|
||||
# Since this dialog is shown through as ShowModal(),
|
||||
# we hook into the Validate function to veto the closing of the dialog until we're ready.
|
||||
# This always returns False, and when we're ready will EndModal()
|
||||
selected = self.GetSelected()
|
||||
options = self.GetOptions()
|
||||
@@ -185,3 +189,10 @@ class CopySelectDialog(wx.Dialog):
|
||||
def exportEfs(self, options, callback):
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
EfsPort.exportEfs(fit, 0, callback)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def exportFitStats(self, options, callback):
|
||||
""" Puts fit stats in textual format into the clipboard """
|
||||
fit = getFit(self.mainFrame.getActiveFit())
|
||||
Port.exportFitStats(fit, callback)
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ from service.port.eft import (
|
||||
from service.port.esi import exportESI, importESI
|
||||
from service.port.multibuy import exportMultiBuy
|
||||
from service.port.shared import IPortUser, UserCancelException, processing_notify
|
||||
from service.port.shipstats import exportFitStats
|
||||
from service.port.xml import importXml, exportXml
|
||||
from service.port.muta import parseMutant
|
||||
|
||||
@@ -317,3 +318,7 @@ class Port:
|
||||
@staticmethod
|
||||
def exportMultiBuy(fit, options, callback=None):
|
||||
return exportMultiBuy(fit, options, callback=callback)
|
||||
|
||||
@staticmethod
|
||||
def exportFitStats(fit, callback=None):
|
||||
return exportFitStats(fit, callback=callback)
|
||||
191
service/port/shipstats.py
Normal file
191
service/port/shipstats.py
Normal file
@@ -0,0 +1,191 @@
|
||||
from functools import reduce
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
|
||||
tankTypes = ("shield", "armor", "hull")
|
||||
damageTypes = ("em", "thermal", "kinetic", "explosive")
|
||||
damagePatterns = [DamagePattern.oneType(damageType) for damageType in damageTypes]
|
||||
damageTypeResonanceNames = [damageType.capitalize() + "DamageResonance" for damageType in damageTypes]
|
||||
resonanceNames = {"shield": ["shield" + s for s in damageTypeResonanceNames],
|
||||
"armor": ["armor" + s for s in damageTypeResonanceNames],
|
||||
"hull": [s[0].lower() + s[1:] for s in damageTypeResonanceNames]}
|
||||
|
||||
|
||||
def firepowerSection(fit):
|
||||
""" Returns the text of the firepower section"""
|
||||
totalDps = fit.getTotalDps().total
|
||||
weaponDps = fit.getWeaponDps().total
|
||||
droneDps = fit.getDroneDps().total
|
||||
totalVolley = fit.getTotalVolley().total
|
||||
firepower = [totalDps, weaponDps, droneDps, totalVolley]
|
||||
|
||||
firepowerStr = [formatAmount(dps, 3, 0, 0) for dps in firepower]
|
||||
# showWeaponAndDroneDps = (weaponDps > 0) and (droneDps > 0)
|
||||
if sum(firepower) == 0:
|
||||
return ""
|
||||
|
||||
return "DPS: {} (".format(firepowerStr[0]) + \
|
||||
("Weapon: {}, Drone: {}, ".format(*firepowerStr[1:3])) + \
|
||||
("Volley: {})\n".format(firepowerStr[3]))
|
||||
|
||||
|
||||
def tankSection(fit):
|
||||
""" Returns the text of the tank section"""
|
||||
ehp = [fit.ehp[tank] for tank in tankTypes] if fit.ehp is not None else [0, 0, 0]
|
||||
ehp.append(sum(ehp))
|
||||
ehpStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehp]
|
||||
resists = {tankType: [1 - fit.ship.getModifiedItemAttr(s) for s in resonanceNames[tankType]] for tankType in tankTypes}
|
||||
ehpAgainstDamageType = [sum(pattern.calculateEhp(fit).values()) for pattern in damagePatterns]
|
||||
ehpAgainstDamageTypeStr = [formatAmount(ehpVal, 3, 0, 9) for ehpVal in ehpAgainstDamageType]
|
||||
|
||||
# not used for now. maybe will be improved later
|
||||
# def formattedOutput():
|
||||
# return \
|
||||
# " {:>7} {:>7} {:>7} {:>7} {:>7}\n".format("TOTAL", "EM", "THERM", "KIN", "EXP") + \
|
||||
# "EHP {:>7} {:>7} {:>7} {:>7} {:>7}\n".format(ehpStr[3], *ehpAgainstDamageTypeStr) + \
|
||||
# "Shield {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[0], *resists["shield"]) + \
|
||||
# "Armor {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[1], *resists["armor"]) + \
|
||||
# "Hull {:>7} {:>7.0%} {:>7.0%} {:>7.0%} {:>7.0%}\n".format(ehpStr[2], *resists["hull"])
|
||||
|
||||
def generalOutput():
|
||||
return \
|
||||
"EHP: {:>} (Em: {:>}, Th: {:>}, Kin: {:>}, Exp: {:>}\n".format(ehpStr[3], *ehpAgainstDamageTypeStr) + \
|
||||
"Shield: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%}\n".format(ehpStr[0], *resists["shield"]) + \
|
||||
"Armor: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%}\n".format(ehpStr[1], *resists["armor"]) + \
|
||||
"Hull: {:>} (Em: {:.0%}, Th: {:.0%}, Kin: {:.0%}, Exp: {:.0%}\n".format(ehpStr[2], *resists["hull"])
|
||||
|
||||
return generalOutput()
|
||||
|
||||
|
||||
def repsSection(fit):
|
||||
""" Returns the text of the repairs section"""
|
||||
selfRep = [fit.effectiveTank[tankType + "Repair"] for tankType in tankTypes]
|
||||
sustainRep = [fit.effectiveSustainableTank[tankType + "Repair"] for tankType in tankTypes]
|
||||
remoteRepObj = fit.getRemoteReps()
|
||||
remoteRep = [remoteRepObj.shield, remoteRepObj.armor, remoteRepObj.hull]
|
||||
shieldRegen = [fit.effectiveSustainableTank["passiveShield"], 0, 0]
|
||||
shieldRechargeModuleMultipliers = [module.item.attributes["shieldRechargeRateMultiplier"].value for module in
|
||||
fit.modules if
|
||||
module.item and "shieldRechargeRateMultiplier" in module.item.attributes]
|
||||
shieldRechargeMultiplierByModules = reduce(lambda x, y: x * y, shieldRechargeModuleMultipliers, 1)
|
||||
if shieldRechargeMultiplierByModules >= 0.9: # If the total affect of modules on the shield recharge is negative or insignificant, we don't care about it
|
||||
shieldRegen[0] = 0
|
||||
totalRep = list(zip(selfRep, remoteRep, shieldRegen))
|
||||
totalRep = list(map(sum, totalRep))
|
||||
|
||||
selfRep.append(sum(selfRep))
|
||||
sustainRep.append(sum(sustainRep))
|
||||
remoteRep.append(sum(remoteRep))
|
||||
shieldRegen.append(sum(shieldRegen))
|
||||
totalRep.append(sum(totalRep))
|
||||
|
||||
totalSelfRep = selfRep[-1]
|
||||
totalRemoteRep = remoteRep[-1]
|
||||
totalShieldRegen = shieldRegen[-1]
|
||||
|
||||
text = ""
|
||||
|
||||
if sum(totalRep) > 0: # Most commonly, there are no reps at all; then we skip this section
|
||||
singleTypeRep = None
|
||||
singleTypeRepName = None
|
||||
if totalRemoteRep == 0 and totalShieldRegen == 0: # Only self rep
|
||||
singleTypeRep = selfRep[:-1]
|
||||
singleTypeRepName = "Self"
|
||||
if totalSelfRep == 0 and totalShieldRegen == 0: # Only remote rep
|
||||
singleTypeRep = remoteRep[:-1]
|
||||
singleTypeRepName = "Remote"
|
||||
if totalSelfRep == 0 and totalRemoteRep == 0: # Only shield regen
|
||||
singleTypeRep = shieldRegen[:-1]
|
||||
singleTypeRepName = "Regen"
|
||||
if singleTypeRep and sum(
|
||||
x > 0 for x in singleTypeRep) == 1: # Only one type of reps and only one tank type is repaired
|
||||
index = next(i for i, v in enumerate(singleTypeRep) if v > 0)
|
||||
if singleTypeRepName == "Regen":
|
||||
text += "Shield regeneration: {} EHP/s".format(formatAmount(singleTypeRep[index], 3, 0, 9))
|
||||
else:
|
||||
text += "{} {} repair: {} EHP/s".format(singleTypeRepName, tankTypes[index],
|
||||
formatAmount(singleTypeRep[index], 3, 0, 9))
|
||||
if (singleTypeRepName == "Self") and (sustainRep[index] != singleTypeRep[index]):
|
||||
text += " (Sustained: {} EHP/s)".format(formatAmount(sustainRep[index], 3, 0, 9))
|
||||
text += "\n"
|
||||
else: # Otherwise show a table
|
||||
selfRepStr = [formatAmount(rep, 3, 0, 9) for rep in selfRep]
|
||||
sustainRepStr = [formatAmount(rep, 3, 0, 9) for rep in sustainRep]
|
||||
remoteRepStr = [formatAmount(rep, 3, 0, 9) for rep in remoteRep]
|
||||
shieldRegenStr = [formatAmount(rep, 3, 0, 9) if rep != 0 else "" for rep in shieldRegen]
|
||||
totalRepStr = [formatAmount(rep, 3, 0, 9) for rep in totalRep]
|
||||
|
||||
header = "REPS "
|
||||
lines = [
|
||||
"Shield ",
|
||||
"Armor ",
|
||||
"Hull ",
|
||||
"Total "
|
||||
]
|
||||
|
||||
showSelfRepColumn = totalSelfRep > 0
|
||||
showSustainRepColumn = sustainRep != selfRep
|
||||
showRemoteRepColumn = totalRemoteRep > 0
|
||||
showShieldRegenColumn = totalShieldRegen > 0
|
||||
|
||||
if showSelfRepColumn + showSustainRepColumn + showRemoteRepColumn + showShieldRegenColumn > 1:
|
||||
header += "{:>7} ".format("TOTAL")
|
||||
lines = [line + "{:>7} ".format(rep) for line, rep in zip(lines, totalRepStr)]
|
||||
if showSelfRepColumn:
|
||||
header += "{:>7} ".format("SELF")
|
||||
lines = [line + "{:>7} ".format(rep) for line, rep in zip(lines, selfRepStr)]
|
||||
if showSustainRepColumn:
|
||||
header += "{:>7} ".format("SUST")
|
||||
lines = [line + "{:>7} ".format(rep) for line, rep in zip(lines, sustainRepStr)]
|
||||
if showRemoteRepColumn:
|
||||
header += "{:>7} ".format("REMOTE")
|
||||
lines = [line + "{:>7} ".format(rep) for line, rep in zip(lines, remoteRepStr)]
|
||||
if showShieldRegenColumn:
|
||||
header += "{:>7} ".format("REGEN")
|
||||
lines = [line + "{:>7} ".format(rep) for line, rep in zip(lines, shieldRegenStr)]
|
||||
|
||||
text += header + "\n"
|
||||
repsByTank = zip(totalRep, selfRep, sustainRep, remoteRep, shieldRegen)
|
||||
for line in lines:
|
||||
reps = next(repsByTank)
|
||||
if sum(reps) > 0:
|
||||
text += line + "\n"
|
||||
return text
|
||||
|
||||
|
||||
def miscSection(fit):
|
||||
text = ""
|
||||
text += "Speed: {} m/s\n".format(formatAmount(fit.maxSpeed, 3, 0, 0))
|
||||
text += "Signature: {} m\n".format(formatAmount(fit.ship.getModifiedItemAttr("signatureRadius"), 3, 0, 9))
|
||||
|
||||
text += "Capacitor: {} GJ".format(formatAmount(fit.ship.getModifiedItemAttr("capacitorCapacity"), 3, 0, 9))
|
||||
capState = fit.capState
|
||||
if fit.capStable:
|
||||
text += " (Stable at {0:.0f}%)".format(capState)
|
||||
else:
|
||||
text += " (Lasts {})".format("%ds" % capState if capState <= 60 else "%dm%ds" % divmod(capState, 60))
|
||||
text += "\n"
|
||||
|
||||
text += "Targeting range: {} km\n".format(formatAmount(fit.maxTargetRange / 1000, 3, 0, 0))
|
||||
text += "Scan resolution: {0:.0f} mm\n".format(fit.ship.getModifiedItemAttr("scanResolution"))
|
||||
text += "Sensor strength: {}\n".format(formatAmount(fit.scanStrength, 3, 0, 0))
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def exportFitStats(fit, callback):
|
||||
"""
|
||||
Returns the text of the stats export of the given fit
|
||||
"""
|
||||
sections = filter(None, (firepowerSection(fit), # Prune empty sections
|
||||
tankSection(fit),
|
||||
repsSection(fit),
|
||||
miscSection(fit)))
|
||||
|
||||
text = "{} ({})\n".format(fit.name, fit.ship.name) + "\n"
|
||||
text += "\n".join(sections)
|
||||
|
||||
if callback:
|
||||
callback(text)
|
||||
else:
|
||||
return text
|
||||
Reference in New Issue
Block a user