Compare commits

..

97 Commits

Author SHA1 Message Date
DarkPhoenix
31cfa7c93a Bump version 2024-11-26 19:17:50 +01:00
DarkPhoenix
da1d42b578 Fix drone sorter for EFT export 2024-11-26 19:16:44 +01:00
DarkPhoenix
502e5a8d8e Update static data 2024-11-26 19:03:00 +01:00
DarkPhoenix
087867bdca Update static data and icons 2024-11-14 14:12:20 +01:00
DarkPhoenix
86ad17b075 Expand breacher tooltip 2024-11-13 15:37:40 +01:00
DarkPhoenix
b8ad4772b8 Bump version 2024-11-13 12:32:24 +01:00
DarkPhoenix
e0e5be5870 Change tooltip 2024-11-13 12:32:07 +01:00
DarkPhoenix
8b2f7c56db Update effects 2024-11-13 12:30:51 +01:00
DarkPhoenix
0ca1a675c2 Update static data 2024-11-13 12:30:28 +01:00
DarkPhoenix
6a0bfce262 Expand radius tooltip 2024-11-13 11:02:02 +01:00
DarkPhoenix
362aebe658 Increase size of inputs 2024-11-13 10:35:09 +01:00
DarkPhoenix
bbe3e931f7 Bump wxpython version 2024-11-13 05:58:11 +01:00
DarkPhoenix
f5743af6a4 Cache calculated values 2024-11-13 05:57:05 +01:00
DarkPhoenix
dbc2993fb9 Support inflicted damage for breachers 2024-11-13 05:35:41 +01:00
DarkPhoenix
219173c43e A few fixes for breachers on graphs 2024-11-13 05:13:37 +01:00
DarkPhoenix
6074cfe15a Rework damage stats calculation to always expose full breacher info 2024-11-13 04:37:15 +01:00
DarkPhoenix
f0a72f4307 Add full HP parameter to graphs (for now, unused) 2024-11-12 17:14:52 +01:00
DarkPhoenix
ecc3f9fa7e Add base support for breacher pods to graphs 2024-11-12 16:58:18 +01:00
DarkPhoenix
c660e4058c Fix graphs 2024-11-12 14:49:52 +01:00
DarkPhoenix
7664b00b59 Minor fixes 2024-11-12 14:45:41 +01:00
DarkPhoenix
5721beacf5 Expose breacher DPS and volley to stats 2024-11-12 14:20:20 +01:00
DarkPhoenix
13f3793515 Implement breacher logic on module level 2024-11-11 21:17:32 +01:00
DarkPhoenix
f8e0520344 Add HP value to target profile editor 2024-11-11 15:07:40 +01:00
DarkPhoenix
2c8e306dc2 Add HP field to target profile 2024-11-11 14:34:38 +01:00
DarkPhoenix
7c37d8fd74 Add a few bits to breacher volley calculation 2024-11-11 14:12:06 +01:00
DarkPhoenix
88129c0df4 Do not choke on mutated drones in drone panel, since they lost market group 2024-11-10 12:10:10 +01:00
DarkPhoenix
fa67efd9cb Revert wxpython version change, since binary wheels seem to be unavailable for it yet 2024-11-09 23:54:00 +01:00
DarkPhoenix
38a345df93 Allow release info change on linux 2024-11-09 17:29:53 +01:00
DarkPhoenix
b41ec8a5a9 Bump version 2024-11-09 16:45:26 +01:00
DarkPhoenix
4ba9bb7b99 Add info to misc column 2024-11-09 16:37:46 +01:00
DarkPhoenix
f3c8f485b9 Implement breacher pod skill effects 2024-11-09 12:01:28 +01:00
DarkPhoenix
8d204dd173 Add deathless faction icon 2024-11-09 11:37:24 +01:00
DarkPhoenix
f870d7e6d2 Add effects for new ships 2024-11-09 11:28:29 +01:00
DarkPhoenix
715997041a Update icons 2024-11-09 09:23:19 +01:00
DarkPhoenix
003191d9a4 Update effects for everything but new ships 2024-11-09 09:21:48 +01:00
DarkPhoenix
9302d79e1d Sort glorified mutaplasmids right after their base versions, and shorten them to Gl. 2024-11-09 09:04:18 +01:00
DarkPhoenix
401fc671d4 Fix a few regex warnings 2024-11-09 08:40:55 +01:00
DarkPhoenix
fbadcf0af6 Update static data 2024-11-09 08:28:44 +01:00
DarkPhoenix
c7f600f88c Bump wxpython version 2024-11-02 08:15:33 +01:00
DarkPhoenix
229dd0ddae Fix sorter for structure fighters 2024-11-02 08:15:21 +01:00
DarkPhoenix
8883b18025 Bump version 2024-10-31 00:50:26 +01:00
DarkPhoenix
ea9b67cedd Implement python effects 2024-10-31 00:50:08 +01:00
DarkPhoenix
c4cf1d1ecb Implement Cobra effects 2024-10-31 00:32:50 +01:00
DarkPhoenix
f76f7e85db Add ability to override character security status via a menu 2024-10-31 00:08:31 +01:00
DarkPhoenix
b807e2a36b Add on-fit pilot security attribute, and use it in effects 2024-10-30 20:09:26 +01:00
DarkPhoenix
2bb35a15ce Change lower limit of SS to be -10 instead of -5 2024-10-30 19:12:19 +01:00
DarkPhoenix
376bda7a94 Implement sidewinder effects 2024-10-30 19:10:25 +01:00
DarkPhoenix
a642bdc8c9 Move prize ships to separate category 2024-10-30 18:16:14 +01:00
DarkPhoenix
71f3e7b858 Update icons 2024-10-30 18:12:47 +01:00
DarkPhoenix
88f5ed2da5 Update staticdata 2024-10-30 18:04:41 +01:00
DarkPhoenix
3b9bbae0c3 Bump version 2024-10-03 13:37:54 +02:00
DarkPhoenix
6d474492ea Update effects file 2024-10-03 13:37:25 +02:00
DarkPhoenix
ebc61efef8 Update static data 2024-10-03 13:28:30 +02:00
DarkPhoenix
7b5c95213f Bump version 2024-09-18 13:54:33 +02:00
DarkPhoenix
1312177e0e Add new renames 2024-09-18 13:42:53 +02:00
DarkPhoenix
5b0df0a9c3 Update static data to 2682489 2024-09-18 13:33:03 +02:00
DarkPhoenix
1ea6136cb2 Update static data 2024-09-11 16:32:13 +02:00
DarkPhoenix
da2b6bbb6d Bump version 2024-09-10 00:01:40 +02:00
DarkPhoenix
87007af5f8 Pass package path to importlib call, not module path 2024-09-09 23:35:07 +02:00
DarkPhoenix
dd5ab872b1 Reapply "Make it 3.9+, but get rid of deprecation warnings"
This reverts commit 65fb46885a.
2024-09-09 23:31:19 +02:00
DarkPhoenix
65fb46885a Revert "Make it 3.9+, but get rid of deprecation warnings"
This reverts commit 01354a0a83.
2024-09-09 23:00:24 +02:00
DarkPhoenix
fad1e401b2 Bump version 2024-09-09 20:25:02 +02:00
DarkPhoenix
d59f8afd8a Update icons 2024-09-09 20:24:33 +02:00
DarkPhoenix
7c42d00219 Update static data 2024-09-09 20:20:44 +02:00
DarkPhoenix
777ba69ac4 Ensure context menus can be called on items w/o market group 2024-07-20 19:09:00 +02:00
DarkPhoenix
9a3bde872b Fix regex pattern to remove syntax warning 2024-07-09 23:23:49 +02:00
DarkPhoenix
01354a0a83 Make it 3.9+, but get rid of deprecation warnings 2024-07-09 21:49:05 +02:00
DarkPhoenix
8c3b8589d5 Adjust jargon loader to be py3.12-compatible 2024-07-09 21:46:39 +02:00
Anton Vorobyov
451b5d4312 Merge pull request #2624 from lunedis/feature/charges-for-fit
Add "Charges For Active Fit" market tree shortcut
2024-07-09 23:31:53 +04:00
Anton Vorobyov
d204e70afc Merge pull request #2614 from kgasiorowski/issue/2613
Rename Industrial to Hauler
2024-07-09 23:29:02 +04:00
Karl Werner
ce9ce17ad2 use mode to limit refresh 2024-07-07 14:41:16 +02:00
Karl Werner
6a7cdda91a add comments 2024-07-06 16:01:58 +02:00
Karl Werner
f38c61da51 update on fit change, new icon 2024-07-06 15:52:18 +02:00
Karl Werner
735827a25b proof of concept 2024-07-06 12:12:48 +02:00
DarkPhoenix
d3fcdcbe47 Marshal bonus is not stacking penalized 2024-06-29 23:50:03 +02:00
DarkPhoenix
a3dce73663 Update static data 2024-06-27 21:21:35 +02:00
Kuba Gasiorowski
594b58388f Merge branch 'master' into issue/2613 2024-06-14 18:40:49 -04:00
DarkPhoenix
fa6be2edfb Bump version 2024-06-13 16:20:49 +02:00
DarkPhoenix
1177968b02 Update renders 2024-06-13 16:20:29 +02:00
DarkPhoenix
c04b370410 Update static data 2024-06-13 16:15:45 +02:00
DarkPhoenix
038b95531d Bump version 2024-06-12 16:04:00 +02:00
DarkPhoenix
56bc5c3376 Fix icons script and add missing renders 2024-06-12 16:03:09 +02:00
DarkPhoenix
f5d8be7861 Update static data and add new effect 2024-06-12 15:11:15 +02:00
Kuba Gasiorowski
5366c23db2 Merge branch 'master' into issue/2613 2024-06-11 14:03:24 -04:00
DarkPhoenix
d147db22f1 Bump version 2024-06-11 16:33:10 +02:00
DarkPhoenix
28ebcd2739 Fix upwell ship race detection 2024-06-08 03:12:34 +02:00
DarkPhoenix
2f57eb6ea6 Bump version 2024-06-08 03:01:13 +02:00
DarkPhoenix
3a7ee4699e Update effects and related things 2024-06-08 03:00:45 +02:00
DarkPhoenix
fa7f991fae Update icons 2024-06-08 01:37:13 +02:00
DarkPhoenix
0be8b429aa Add renames 2024-06-08 01:19:22 +02:00
DarkPhoenix
7d94e2de7d Update static data to 2604967 2024-06-08 00:57:34 +02:00
Kuba Gasiorowski
6d32db5827 Rename Industrial to Hauler 2024-05-26 23:45:27 -04:00
DarkPhoenix
0fbb318d8a Bump version 2024-04-30 04:10:53 +06:00
DarkPhoenix
cd013e8287 Add new effects 2024-04-30 04:10:33 +06:00
DarkPhoenix
e667453c1e Add new icons 2024-04-30 03:59:25 +06:00
DarkPhoenix
24e2db0f88 Update static data to 2563119 2024-04-30 03:55:34 +06:00
DarkPhoenix
3c47f8c6bb Add OS version comments to windows manifest 2024-04-17 02:42:42 +06:00
316 changed files with 175378 additions and 23960 deletions

View File

@@ -17,7 +17,7 @@ for:
# init:
# - sh: curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
install:
- sh: sudo DEBIAN_FRONTEND=noninteractive apt-get -y update
- sh: sudo DEBIAN_FRONTEND=noninteractive apt-get -y update --allow-releaseinfo-change
# AppImage dependencies
- sh: sudo DEBIAN_FRONTEND=noninteractive apt-get -y install libfuse2
# Preparation script dependencies

View File

@@ -544,7 +544,7 @@ def update_db():
continue
typeName = row.get('typeName_en-us', '')
# Regular sets matching
m = re.match('(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
m = re.match(r'(?P<grade>(High|Mid|Low)-grade) (?P<set>\w+) (?P<implant>(Alpha|Beta|Gamma|Delta|Epsilon|Omega))', typeName, re.IGNORECASE)
if m:
implantSets.setdefault((m.group('grade'), m.group('set')), set()).add(row['typeID'])
# Special set matching

View File

@@ -10,10 +10,15 @@
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>

View File

@@ -0,0 +1,15 @@
"""
Migration 48
- added pilot security column (CONCORD ships)
"""
import sqlalchemy
def upgrade(saveddata_engine):
try:
saveddata_engine.execute("SELECT pilotSecurity FROM fits LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN pilotSecurity FLOAT")

View File

@@ -0,0 +1,15 @@
"""
Migration 49
- added hp column to targetResists table
"""
import sqlalchemy
def upgrade(saveddata_engine):
try:
saveddata_engine.execute("SELECT hp FROM targetResists LIMIT 1;")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE targetResists ADD COLUMN hp FLOAT;")

View File

@@ -63,7 +63,8 @@ fits_table = Table("fits", saveddata_meta,
Column("ignoreRestrictions", Boolean, default=0),
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
Column("modified", DateTime, nullable=True, default=datetime.datetime.now, onupdate=datetime.datetime.now),
Column("systemSecurity", Integer, nullable=True)
Column("systemSecurity", Integer, nullable=True),
Column("pilotSecurity", Float, nullable=True),
)
projectedFits_table = Table("projectedFits", saveddata_meta,

View File

@@ -37,6 +37,7 @@ targetProfiles_table = Table(
Column('maxVelocity', Float, nullable=True),
Column('signatureRadius', Float, nullable=True),
Column('radius', Float, nullable=True),
Column('hp', Float, nullable=True),
Column('ownerID', ForeignKey('users.ID'), nullable=True),
Column('created', DateTime, nullable=True, default=datetime.datetime.now),
Column('modified', DateTime, nullable=True, onupdate=datetime.datetime.now))
@@ -48,4 +49,5 @@ mapper(
'rawName': targetProfiles_table.c.name,
'_maxVelocity': targetProfiles_table.c.maxVelocity,
'_signatureRadius': targetProfiles_table.c.signatureRadius,
'_radius': targetProfiles_table.c.radius})
'_radius': targetProfiles_table.c.radius,
'_hp': targetProfiles_table.c.hp})

File diff suppressed because it is too large Load Diff

View File

@@ -343,7 +343,9 @@ class Item(EqBase):
500018: "mordu",
500019: "sansha",
500020: "serpentis",
500026: "triglavian"
500026: "triglavian",
500027: "upwell",
500029: "deathless",
}
@property
@@ -351,11 +353,7 @@ class Item(EqBase):
if self.__race is None:
try:
if (
self.category.name == 'Structure' or
# Here until CCP puts their shit together
self.name in ("Thunderchild", "Stormbringer", "Skybreaker")
):
if self.category.name == 'Structure':
self.__race = "upwell"
else:
self.__race = self.factionMap[self.factionID]
@@ -377,7 +375,8 @@ class Item(EqBase):
16 : "jove",
32 : "sansha", # Incrusion Sansha
128: "ore",
135: "triglavian"
135: "triglavian",
168: "upwell",
}
# Race is None by default
race = None
@@ -571,13 +570,18 @@ class DynamicItem(EqBase):
@property
def shortName(self):
name = self.item.customName
keywords = ('Decayed', 'Gravid', 'Unstable', 'Radical')
keywords = (
'Decayed', 'Glorified Decayed',
'Gravid', 'Glorified Gravid',
'Unstable', 'Glorified Unstable',
'Radical', 'Glorified Radical')
for kw in keywords:
if name.startswith(f'{kw} '):
name = kw
m = re.match('(?P<mutagrade>\S+) (?P<dronetype>\S+) Drone (?P<mutatype>\S+) Mutaplasmid', name)
m = re.match(r'(?P<mutagrade>(Glorified )?\S+) (?P<dronetype>\S+) Drone (?P<mutatype>\S+) Mutaplasmid', name)
if m:
name = '{} {}'.format(m.group('mutagrade'), m.group('mutatype'))
name = name.replace('Glorified ', 'Gl. ')
return name

View File

@@ -19,6 +19,7 @@
import math
from copy import deepcopy
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
@@ -161,7 +162,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def getVolleyParameters(self, targetProfile=None):
if not self.dealsDamage or self.amountActive <= 0:
return {0: DmgTypes(0, 0, 0, 0)}
return {0: DmgTypes.default()}
if self.__baseVolley is None:
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
@@ -170,11 +171,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
volley = DmgTypes(
em=self.__baseVolley.em * (1 - getattr(targetProfile, "emAmount", 0)),
thermal=self.__baseVolley.thermal * (1 - getattr(targetProfile, "thermalAmount", 0)),
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetProfile, "kineticAmount", 0)),
explosive=self.__baseVolley.explosive * (1 - getattr(targetProfile, "explosiveAmount", 0)))
volley = deepcopy(self.__baseVolley)
volley.profile = targetProfile
return {0: volley}
def getVolley(self, targetProfile=None):
@@ -183,16 +181,12 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def getDps(self, targetProfile=None):
volley = self.getVolley(targetProfile=targetProfile)
if not volley:
return DmgTypes(0, 0, 0, 0)
return DmgTypes.default()
cycleParams = self.getCycleParameters()
if cycleParams is None:
return DmgTypes(0, 0, 0, 0)
return DmgTypes.default()
dpsFactor = 1 / (cycleParams.averageTime / 1000)
dps = DmgTypes(
em=volley.em * dpsFactor,
thermal=volley.thermal * dpsFactor,
kinetic=volley.kinetic * dpsFactor,
explosive=volley.explosive * dpsFactor)
dps = volley * dpsFactor
return dps
def isRemoteRepping(self, ignoreState=False):

View File

@@ -19,6 +19,7 @@
import math
from copy import deepcopy
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
@@ -198,16 +199,14 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
for ability in self.abilities:
# Not passing resists here as we want to calculate and store base volley
self.__baseVolley[ability.effectID] = {0: ability.getVolley()}
adjustedVolley = {}
adjustedVolleys = {}
for effectID, effectData in self.__baseVolley.items():
adjustedVolley[effectID] = {}
for volleyTime, volleyValue in effectData.items():
adjustedVolley[effectID][volleyTime] = DmgTypes(
em=volleyValue.em * (1 - getattr(targetProfile, "emAmount", 0)),
thermal=volleyValue.thermal * (1 - getattr(targetProfile, "thermalAmount", 0)),
kinetic=volleyValue.kinetic * (1 - getattr(targetProfile, "kineticAmount", 0)),
explosive=volleyValue.explosive * (1 - getattr(targetProfile, "explosiveAmount", 0)))
return adjustedVolley
adjustedVolleys[effectID] = {}
for volleyTime, baseVolley in effectData.items():
adjustedVolley = deepcopy(baseVolley)
adjustedVolley.profile = targetProfile
adjustedVolleys[effectID][volleyTime] = adjustedVolley
return adjustedVolleys
def getVolleyPerEffect(self, targetProfile=None):
volleyParams = self.getVolleyParametersPerEffect(targetProfile=targetProfile)
@@ -218,28 +217,16 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def getVolley(self, targetProfile=None):
volleyParams = self.getVolleyParametersPerEffect(targetProfile=targetProfile)
em = 0
therm = 0
kin = 0
exp = 0
volley = DmgTypes.default()
for volleyData in volleyParams.values():
em += volleyData[0].em
therm += volleyData[0].thermal
kin += volleyData[0].kinetic
exp += volleyData[0].explosive
return DmgTypes(em, therm, kin, exp)
volley += volleyData[0]
return volley
def getDps(self, targetProfile=None):
em = 0
thermal = 0
kinetic = 0
explosive = 0
for dps in self.getDpsPerEffect(targetProfile=targetProfile).values():
em += dps.em
thermal += dps.thermal
kinetic += dps.kinetic
explosive += dps.explosive
return DmgTypes(em=em, thermal=thermal, kinetic=kinetic, explosive=explosive)
dps = DmgTypes.default()
for subdps in self.getDpsPerEffect(targetProfile=targetProfile).values():
dps += subdps
return dps
def getDpsPerEffect(self, targetProfile=None):
if not self.active or self.amount <= 0:

View File

@@ -116,7 +116,7 @@ class FighterAbility:
def getVolley(self, targetProfile=None):
if not self.dealsDamage or not self.active:
return DmgTypes(0, 0, 0, 0)
return DmgTypes.default()
if self.attrPrefix == "fighterAbilityLaunchBomb":
em = self.fighter.getModifiedChargeAttr("emDamage", 0)
therm = self.fighter.getModifiedChargeAttr("thermalDamage", 0)
@@ -128,24 +128,17 @@ class FighterAbility:
kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix), 0)
exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix), 0)
dmgMult = self.fighter.amount * self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix), 1)
volley = DmgTypes(
em=em * dmgMult * (1 - getattr(targetProfile, "emAmount", 0)),
thermal=therm * dmgMult * (1 - getattr(targetProfile, "thermalAmount", 0)),
kinetic=kin * dmgMult * (1 - getattr(targetProfile, "kineticAmount", 0)),
explosive=exp * dmgMult * (1 - getattr(targetProfile, "explosiveAmount", 0)))
volley = DmgTypes(em=em * dmgMult, thermal=therm * dmgMult, kinetic=kin * dmgMult, explosive=exp * dmgMult)
volley.profile = targetProfile
return volley
def getDps(self, targetProfile=None, cycleTimeOverride=None):
volley = self.getVolley(targetProfile=targetProfile)
if not volley:
return DmgTypes(0, 0, 0, 0)
return DmgTypes.default()
cycleTime = cycleTimeOverride if cycleTimeOverride is not None else self.cycleTime
dpsFactor = 1 / (cycleTime / 1000)
dps = DmgTypes(
em=volley.em * dpsFactor,
thermal=volley.thermal * dpsFactor,
kinetic=volley.kinetic * dpsFactor,
explosive=volley.explosive * dpsFactor)
dps = volley * dpsFactor
return dps
def clear(self):

View File

@@ -1688,27 +1688,33 @@ class Fit:
self.__droneWaste = droneWaste
def calculateWeaponDmgStats(self, spoolOptions):
weaponVolley = DmgTypes(0, 0, 0, 0)
weaponDps = DmgTypes(0, 0, 0, 0)
weaponVolley = DmgTypes.default()
weaponDps = DmgTypes.default()
for mod in self.modules:
weaponVolley += mod.getVolley(spoolOptions=spoolOptions, targetProfile=self.targetProfile)
weaponDps += mod.getDps(spoolOptions=spoolOptions, targetProfile=self.targetProfile)
weaponVolley += mod.getVolley(spoolOptions=spoolOptions)
weaponDps += mod.getDps(spoolOptions=spoolOptions)
weaponVolley.profile = self.targetProfile
weaponDps.profile = self.targetProfile
self.__weaponVolleyMap[spoolOptions] = weaponVolley
self.__weaponDpsMap[spoolOptions] = weaponDps
def calculateDroneDmgStats(self):
droneVolley = DmgTypes(0, 0, 0, 0)
droneDps = DmgTypes(0, 0, 0, 0)
droneVolley = DmgTypes.default()
droneDps = DmgTypes.default()
for drone in self.drones:
droneVolley += drone.getVolley(targetProfile=self.targetProfile)
droneDps += drone.getDps(targetProfile=self.targetProfile)
droneVolley += drone.getVolley()
droneDps += drone.getDps()
for fighter in self.fighters:
droneVolley += fighter.getVolley(targetProfile=self.targetProfile)
droneDps += fighter.getDps(targetProfile=self.targetProfile)
droneVolley += fighter.getVolley()
droneDps += fighter.getDps()
droneVolley.profile = self.targetProfile
droneDps.profile = self.targetProfile
self.__droneDps = droneDps
self.__droneVolley = droneVolley
@@ -1743,6 +1749,18 @@ class Fit:
secstatus = FitSystemSecurity.NULLSEC
return secstatus
def getPilotSecurity(self, low_limit=-10, high_limit=5):
secstatus = self.pilotSecurity
# Not defined -> use character SS, with 0.0 fallback if it fails
if secstatus is None:
try:
secstatus = self.character.secStatus
except (SystemExit, KeyboardInterrupt):
raise
except:
secstatus = 0
return max(low_limit, min(high_limit, secstatus))
def activeModulesIter(self):
for mod in self.modules:
if mod.state >= FittingModuleState.ACTIVE:
@@ -1824,6 +1842,7 @@ class Fit:
fitCopy.targetProfile = self.targetProfile
fitCopy.implantLocation = self.implantLocation
fitCopy.systemSecurity = self.systemSecurity
fitCopy.pilotSecurity = self.pilotSecurity
fitCopy.notes = self.notes
for i in self.modules:

View File

@@ -33,7 +33,7 @@ from eos.utils.cycles import CycleInfo, CycleSequence
from eos.utils.default import DEFAULT
from eos.utils.float import floatUnerr
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
from eos.utils.stats import DmgTypes, RRTypes
from eos.utils.stats import BreacherInfo, DmgTypes, RRTypes
pyfalog = Logger(__name__)
@@ -453,6 +453,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
return True
return False
@property
def isBreacher(self):
return self.charge and 'dotMissileLaunching' in self.charge.effects
def canDealDamage(self, ignoreState=False):
if self.isEmpty:
return False
@@ -469,75 +473,77 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
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)}
return {0: DmgTypes.default()}
if self.__baseVolley is None:
self.__baseVolley = {}
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
# Some delay attributes have non-0 default value, so we have to pick according to effects
if {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(self.item.effects):
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0)
elif {'doomsdayBeamDOT', 'doomsdaySlash', 'doomsdayConeDOT', 'debuffLance'}.intersection(self.item.effects):
dmgDelay = self.getModifiedItemAttr("doomsdayWarningDuration", 0)
if self.isBreacher:
dmgDelay = 1
subcycles = math.floor(self.getModifiedChargeAttr("dotDuration", 0) / 1000)
breacher_info = BreacherInfo(
absolute=self.getModifiedChargeAttr("dotMaxDamagePerTick", 0),
relative=self.getModifiedChargeAttr("dotMaxHPPercentagePerTick", 0) / 100)
for i in range(subcycles):
volley = DmgTypes.default()
volley.add_breacher(dmgDelay + i, breacher_info)
self.__baseVolley[dmgDelay + i] = volley
else:
dmgDelay = 0
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
# Reaper DD can damage each target only once
if dmgDuration != 0 and dmgSubcycle != 0 and 'doomsdaySlash' not in self.item.effects:
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
else:
subcycles = 1
for i in range(subcycles):
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
em=(dmgGetter("emDamage", 0)) * dmgMult,
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
# Some delay attributes have non-0 default value, so we have to pick according to effects
if {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(self.item.effects):
dmgDelay = self.getModifiedItemAttr("damageDelayDuration", 0)
elif {'doomsdayBeamDOT', 'doomsdaySlash', 'doomsdayConeDOT', 'debuffLance'}.intersection(self.item.effects):
dmgDelay = self.getModifiedItemAttr("doomsdayWarningDuration", 0)
else:
dmgDelay = 0
dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0)
dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0)
# Reaper DD can damage each target only once
if dmgDuration != 0 and dmgSubcycle != 0 and 'doomsdaySlash' not in self.item.effects:
subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle))
else:
subcycles = 1
for i in range(subcycles):
self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes(
em=(dmgGetter("emDamage", 0)) * dmgMult,
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
spoolBoost = calculateSpoolup(
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
spoolMultiplier = 1 + spoolBoost
adjustedVolley = {}
for volleyTime, volleyValue in self.__baseVolley.items():
adjustedVolley[volleyTime] = DmgTypes(
em=volleyValue.em * spoolMultiplier * (1 - getattr(targetProfile, "emAmount", 0)),
thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetProfile, "thermalAmount", 0)),
kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetProfile, "kineticAmount", 0)),
explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetProfile, "explosiveAmount", 0)))
return adjustedVolley
adjustedVolleys = {}
for volleyTime, baseVolley in self.__baseVolley.items():
adjustedVolley = baseVolley * spoolMultiplier
adjustedVolley.profile = targetProfile
adjustedVolleys[volleyTime] = adjustedVolley
return adjustedVolleys
def getVolley(self, spoolOptions=None, targetProfile=None, ignoreState=False):
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState)
if len(volleyParams) == 0:
return DmgTypes(0, 0, 0, 0)
return DmgTypes.default()
return volleyParams[min(volleyParams)]
def getDps(self, spoolOptions=None, targetProfile=None, ignoreState=False, getSpreadDPS=False):
dmgDuringCycle = DmgTypes(0, 0, 0, 0)
def getDps(self, spoolOptions=None, targetProfile=None, ignoreState=False):
dps = DmgTypes.default()
cycleParams = self.getCycleParameters()
if cycleParams is None:
return dmgDuringCycle
return dps
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState)
avgCycleTime = cycleParams.averageTime
if len(volleyParams) == 0 or avgCycleTime == 0:
return dmgDuringCycle
for volleyValue in volleyParams.values():
dmgDuringCycle += volleyValue
dpsFactor = 1 / (avgCycleTime / 1000)
dps = DmgTypes(
em=dmgDuringCycle.em * dpsFactor,
thermal=dmgDuringCycle.thermal * dpsFactor,
kinetic=dmgDuringCycle.kinetic * dpsFactor,
explosive=dmgDuringCycle.explosive * dpsFactor)
if not getSpreadDPS:
return dps
return {'em':dmgDuringCycle.em * dpsFactor,
'therm': dmgDuringCycle.thermal * dpsFactor,
'kin': dmgDuringCycle.kinetic * dpsFactor,
'exp': dmgDuringCycle.explosive * dpsFactor}
if self.isBreacher:
return volleyParams[min(volleyParams)]
for volleyValue in volleyParams.values():
dps += volleyValue
dpsFactor = 1 / (avgCycleTime / 1000)
dps *= dpsFactor
return dps
def isRemoteRepping(self, ignoreState=False):
repParams = self.getRepAmountParameters(ignoreState=ignoreState)
@@ -949,6 +955,13 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
and ((gang and effect.isType("gang")) or not gang):
effect.handler(fit, self, context, projectionRange, effect=effect)
def getCycleParametersForDps(self, reloadOverride=None):
# Special hack for breachers, since those are DoT and work independently of gun cycle
if self.isBreacher:
return CycleInfo(activeTime=1000, inactiveTime=0, quantity=math.inf, isInactivityReload=False)
else:
return self.getCycleParameters(reloadOverride=reloadOverride)
def getCycleParameters(self, reloadOverride=None):
"""Copied from new eos as well"""
# Determine if we'll take into account reload time or not

View File

@@ -254,7 +254,7 @@ class TargetProfile:
def init(self):
self.builtin = False
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None):
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0, maxVelocity=None, signatureRadius=None, radius=None, hp=None):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
self.kineticAmount = kineticAmount
@@ -262,6 +262,7 @@ class TargetProfile:
self._maxVelocity = maxVelocity
self._signatureRadius = signatureRadius
self._radius = radius
self._hp = hp
@classmethod
def getBuiltinList(cls):
@@ -331,6 +332,18 @@ class TargetProfile:
def radius(self, val):
self._radius = val
@property
def hp(self):
if self._hp is None or self._hp == -1:
return math.inf
return self._hp
@hp.setter
def hp(self, val):
if val is not None and math.isinf(val):
val = None
self._hp = val
@classmethod
def importPatterns(cls, text):
lines = re.split('[\n\r]+', text)

View File

@@ -18,6 +18,9 @@
# ===============================================================================
import math
from collections import defaultdict
from eos.utils.float import floatUnerr
from utils.repr import makeReprStr
@@ -26,15 +29,133 @@ def _t(x):
return x
class BreacherInfo:
def __init__(self, absolute, relative):
self.absolute = absolute
self.relative = relative
def __mul__(self, mul):
return type(self)(absolute=self.absolute * mul, relative=self.relative * mul)
def __imul__(self, mul):
if mul == 1:
return self
self.absolute *= mul
self.relative *= mul
return self
def __truediv__(self, div):
return type(self)(absolute=self.absolute / div, relative=self.relative / div)
class DmgTypes:
"""Container for damage data stats."""
"""
Container for volley stats, which stores breacher pod data
in raw form, before application of it to target profile.
"""
def __init__(self, em, thermal, kinetic, explosive):
self.em = em
self.thermal = thermal
self.kinetic = kinetic
self.explosive = explosive
self._calcTotal()
self._em = em
self._thermal = thermal
self._kinetic = kinetic
self._explosive = explosive
self._breachers = defaultdict(lambda: [])
self.__profile = None
# Cached data
self.__cached_em = None
self.__cached_thermal = None
self.__cached_kinetic = None
self.__cached_explosive = None
self.__cached_pure = None
self.__cached_total = None
@classmethod
def default(cls):
return cls(0, 0, 0, 0)
def _clear_cached(self):
self.__cached_em = None
self.__cached_thermal = None
self.__cached_kinetic = None
self.__cached_explosive = None
self.__cached_pure = None
self.__cached_total = None
def add_breacher(self, key, data):
self._breachers[key].append(data)
@property
def profile(self):
return self.__profile
@profile.setter
def profile(self, profile):
self.__profile = profile
self._clear_cached()
@property
def em(self):
if self.__cached_em is not None:
return self.__cached_em
dmg = self._em
if self.profile is not None:
dmg *= 1 - getattr(self.profile, "emAmount", 0)
self.__cached_em = dmg
return dmg
@property
def thermal(self):
if self.__cached_thermal is not None:
return self.__cached_thermal
dmg = self._thermal
if self.profile is not None:
dmg *= 1 - getattr(self.profile, "thermalAmount", 0)
self.__cached_thermal = dmg
return dmg
@property
def kinetic(self):
if self.__cached_kinetic is not None:
return self.__cached_kinetic
dmg = self._kinetic
if self.profile is not None:
dmg *= 1 - getattr(self.profile, "kineticAmount", 0)
self.__cached_kinetic = dmg
return dmg
@property
def explosive(self):
if self.__cached_explosive is not None:
return self.__cached_explosive
dmg = self._explosive
if self.profile is not None:
dmg *= 1 - getattr(self.profile, "explosiveAmount", 0)
self.__cached_explosive = dmg
return dmg
@property
def pure(self):
if self.__cached_pure is not None:
return self.__cached_pure
if self.profile is None:
dmg = sum(
max((b.absolute for b in bs), default=0)
for bs in self._breachers.values())
else:
dmg = sum(
max((min(b.absolute, b.relative * getattr(self.profile, "hp", math.inf)) for b in bs), default=0)
for bs in self._breachers.values())
self.__cached_pure = dmg
return dmg
@property
def total(self):
if self.__cached_total is not None:
return self.__cached_total
dmg = self.em + self.thermal + self.kinetic + self.explosive + self.pure
self.__cached_total = dmg
return dmg
# Iterator is needed to support tuple-style unpacking
def __iter__(self):
@@ -42,6 +163,7 @@ class DmgTypes:
yield self.thermal
yield self.kinetic
yield self.explosive
yield self.pure
yield self.total
def __eq__(self, other):
@@ -50,77 +172,87 @@ class DmgTypes:
# Round for comparison's sake because often damage profiles are
# generated from data which includes float errors
return (
floatUnerr(self.em) == floatUnerr(other.em) and
floatUnerr(self.thermal) == floatUnerr(other.thermal) and
floatUnerr(self.kinetic) == floatUnerr(other.kinetic) and
floatUnerr(self.explosive) == floatUnerr(other.explosive) and
floatUnerr(self.total) == floatUnerr(other.total))
def __bool__(self):
return any((
self.em, self.thermal, self.kinetic,
self.explosive, self.total))
def _calcTotal(self):
self.total = self.em + self.thermal + self.kinetic + self.explosive
floatUnerr(self._em) == floatUnerr(other._em) and
floatUnerr(self._thermal) == floatUnerr(other._thermal) and
floatUnerr(self._kinetic) == floatUnerr(other._kinetic) and
floatUnerr(self._explosive) == floatUnerr(other._explosive) and
sorted(self._breachers) == sorted(other._breachers),
self.profile == other.profile)
def __add__(self, other):
return type(self)(
em=self.em + other.em,
thermal=self.thermal + other.thermal,
kinetic=self.kinetic + other.kinetic,
explosive=self.explosive + other.explosive)
new = type(self)(
em=self._em + other._em,
thermal=self._thermal + other._thermal,
kinetic=self._kinetic + other._kinetic,
explosive=self._explosive + other._explosive)
new.profile = self.profile
for k, v in self._breachers.items():
new._breachers[k].extend(v)
for k, v in other._breachers.items():
new._breachers[k].extend(v)
return new
def __iadd__(self, other):
self.em += other.em
self.thermal += other.thermal
self.kinetic += other.kinetic
self.explosive += other.explosive
self._calcTotal()
self._em += other._em
self._thermal += other._thermal
self._kinetic += other._kinetic
self._explosive += other._explosive
for k, v in other._breachers.items():
self._breachers[k].extend(v)
self._clear_cached()
return self
def __mul__(self, mul):
return type(self)(
em=self.em * mul,
thermal=self.thermal * mul,
kinetic=self.kinetic * mul,
explosive=self.explosive * mul)
new = type(self)(
em=self._em * mul,
thermal=self._thermal * mul,
kinetic=self._kinetic * mul,
explosive=self._explosive * mul)
new.profile = self.profile
for k, v in self._breachers.items():
new._breachers[k] = [b * mul for b in v]
return new
def __imul__(self, mul):
if mul == 1:
return
self.em *= mul
self.thermal *= mul
self.kinetic *= mul
self.explosive *= mul
self._calcTotal()
return self
self._em *= mul
self._thermal *= mul
self._kinetic *= mul
self._explosive *= mul
for v in self._breachers.values():
for b in v:
b *= mul
self._clear_cached()
return self
def __truediv__(self, div):
return type(self)(
em=self.em / div,
thermal=self.thermal / div,
kinetic=self.kinetic / div,
explosive=self.explosive / div)
new = type(self)(
em=self._em / div,
thermal=self._thermal / div,
kinetic=self._kinetic / div,
explosive=self._explosive / div)
new.profile = self.profile
for k, v in self._breachers.items():
new._breachers[k] = [b / div for b in v]
return new
def __itruediv__(self, div):
if div == 1:
return
self.em /= div
self.thermal /= div
self.kinetic /= div
self.explosive /= div
self._calcTotal()
return self
def __bool__(self):
return any((
self._em, self._thermal, self._kinetic, self._explosive,
any(b.absolute or b.relative for b in self._breachers)))
def __repr__(self):
spec = DmgTypes.names()
spec.append('total')
return makeReprStr(self, spec)
class_name = type(self).__name__
return (f'<{class_name}(em={self._em}, thermal={self._thermal}, kinetic={self._kinetic}, '
f'explosive={self._explosive}, breachers={len(self._breachers)})>')
@staticmethod
def names(short=None, postProcessor=None):
def names(short=None, postProcessor=None, includePure=False):
value = [_t('em'), _t('th'), _t('kin'), _t('exp')] if short else [_t('em'), _t('thermal'), _t('kinetic'), _t('explosive')]
if includePure:
value += [_t('pure')]
if postProcessor:
value = [postProcessor(x) for x in value]

View File

@@ -117,7 +117,7 @@ class TimeCache(FitDataCache):
pointData[timeStart] = (dps, volley)
# Gap between items
elif floatUnerr(prevTimeEnd) < floatUnerr(timeStart):
pointData[prevTimeEnd] = (DmgTypes(0, 0, 0, 0), DmgTypes(0, 0, 0, 0))
pointData[prevTimeEnd] = (DmgTypes.default(), DmgTypes.default())
pointData[timeStart] = (dps, volley)
# Changed value
elif dps != prevDps or volley != prevVolley:
@@ -157,7 +157,7 @@ class TimeCache(FitDataCache):
def addDpsVolley(ddKey, addedTimeStart, addedTimeFinish, addedVolleys):
if not addedVolleys:
return
volleySum = sum(addedVolleys, DmgTypes(0, 0, 0, 0))
volleySum = sum(addedVolleys, DmgTypes.default())
if volleySum.total > 0:
addedDps = volleySum / (addedTimeFinish - addedTimeStart)
# We can take "just best" volley, no matter target resistances, because all
@@ -170,24 +170,38 @@ class TimeCache(FitDataCache):
def addDmg(ddKey, addedTime, addedDmg):
if addedDmg.total == 0:
return
addedDmg._breachers = {addedTime + k: v for k, v in addedDmg._breachers.items()}
addedDmg._clear_cached()
intCacheDmg.setdefault(ddKey, {})[addedTime] = addedDmg
# Modules
for mod in src.item.activeModulesIter():
if not mod.isDealingDamage():
continue
cycleParams = mod.getCycleParameters(reloadOverride=True)
cycleParams = mod.getCycleParametersForDps(reloadOverride=True)
if cycleParams is None:
continue
currentTime = 0
nonstopCycles = 0
isBreacher = mod.isBreacher
for cycleTimeMs, inactiveTimeMs, isInactivityReload in cycleParams.iterCycles():
cycleVolleys = []
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
for volleyTimeMs, volley in volleyParams.items():
cycleVolleys.append(volley)
addDmg(mod, currentTime + volleyTimeMs / 1000, volley)
addDpsVolley(mod, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
time = currentTime + volleyTimeMs / 1000
if isBreacher:
time += 1
addDmg(mod, time, volley)
if isBreacher:
break
timeStart = currentTime
timeFinish = currentTime + cycleTimeMs / 1000
if isBreacher:
timeStart += 1
timeFinish += 1
addDpsVolley(mod, timeStart, timeFinish, cycleVolleys)
if inactiveTimeMs > 0:
nonstopCycles = 0
else:

View File

@@ -98,6 +98,8 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
tgt=tgt,
distance=distance,
tgtSigRadius=tgtSigRadius)
elif mod.isBreacher:
applicationMap[mod] = getBreacherMult(mod=mod, distance=distance) if inLockRange else 0
for drone in src.item.activeDronesIter():
if not drone.isDealingDamage():
continue
@@ -192,6 +194,21 @@ def getLauncherMult(mod, distance, tgtSpeed, tgtSigRadius):
return distanceFactor * applicationFactor
def getBreacherMult(mod, distance):
missileMaxRangeData = mod.missileMaxRangeData
if missileMaxRangeData is None:
return 0
# The ranges already consider ship radius
lowerRange, higherRange, higherChance = missileMaxRangeData
if distance is None or distance <= lowerRange:
distanceFactor = 1
elif lowerRange < distance <= higherRange:
distanceFactor = higherChance
else:
distanceFactor = 0
return distanceFactor
def getSmartbombMult(mod, distance):
modRange = mod.maxRange
if modRange is None:

View File

@@ -19,6 +19,7 @@
import eos.config
from eos.saveddata.targetProfile import TargetProfile
from eos.utils.spoolSupport import SpoolOptions, SpoolType
from eos.utils.stats import DmgTypes
from graphs.data.base import PointGetter, SmoothPointGetter
@@ -27,17 +28,16 @@ from .calc.application import getApplicationPerKey
from .calc.projected import getScramRange, getScrammables, getTackledSpeed, getSigRadiusMult
def applyDamage(dmgMap, applicationMap, tgtResists):
total = DmgTypes(em=0, thermal=0, kinetic=0, explosive=0)
def applyDamage(dmgMap, applicationMap, tgtResists, tgtFullHp):
total = DmgTypes.default()
for key, dmg in dmgMap.items():
total += dmg * applicationMap.get(key, 0)
if not GraphSettings.getInstance().get('ignoreResists'):
emRes, thermRes, kinRes, exploRes = tgtResists
total = DmgTypes(
em=total.em * (1 - emRes),
thermal=total.thermal * (1 - thermRes),
kinetic=total.kinetic * (1 - kinRes),
explosive=total.explosive * (1 - exploRes))
else:
emRes = thermRes = kinRes = exploRes = 0
total.profile = TargetProfile(
emAmount=emRes, thermalAmount=thermRes, kineticAmount=kinRes, explosiveAmount=exploRes, hp=tgtFullHp)
return total
@@ -144,7 +144,8 @@ class XDistanceMixin(SmoothPointGetter):
'srcScramRange': getScramRange(src=src) if applyProjected else None,
'tgtScrammables': getScrammables(tgt=tgt) if applyProjected else (),
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
'tgtResists': tgt.getResists()}
'tgtResists': tgt.getResists(),
'tgtFullHp': tgt.getFullHp()}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
@@ -186,7 +187,8 @@ class XDistanceMixin(SmoothPointGetter):
y = applyDamage(
dmgMap=commonData['dmgMap'],
applicationMap=applicationMap,
tgtResists=commonData['tgtResists']).total
tgtResists=commonData['tgtResists'],
tgtFullHp=commonData['tgtFullHp']).total
return y
@@ -241,14 +243,17 @@ class XTimeMixin(PointGetter):
self._prepareTimeCache(src=src, maxTime=maxTime)
timeCache = self._getTimeCacheData(src=src)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
tgtResists = tgt.getResists()
# Custom iteration for time graph to show all data points
currentDmg = None
currentTime = None
for currentTime in sorted(timeCache):
prevDmg = currentDmg
currentDmgData = timeCache[currentTime]
currentDmg = applyDamage(dmgMap=currentDmgData, applicationMap=applicationMap, tgtResists=tgtResists).total
currentDmg = applyDamage(
dmgMap=currentDmgData,
applicationMap=applicationMap,
tgtResists=tgt.getResists(),
tgtFullHp=tgt.getFullHp()).total
if currentTime < minTime:
continue
# First set of data points
@@ -294,7 +299,11 @@ class XTimeMixin(PointGetter):
self._prepareTimeCache(src=src, maxTime=time)
dmgData = self._getTimeCacheDataPoint(src=src, time=time)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
y = applyDamage(dmgMap=dmgData, applicationMap=applicationMap, tgtResists=tgt.getResists()).total
y = applyDamage(
dmgMap=dmgData,
applicationMap=applicationMap,
tgtResists=tgt.getResists(),
tgtFullHp=tgt.getFullHp()).total
return y
@@ -310,7 +319,8 @@ class XTgtSpeedMixin(SmoothPointGetter):
return {
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
'tgtResists': tgt.getResists()}
'tgtResists': tgt.getResists(),
'tgtFullHp': tgt.getFullHp()}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
tgtSpeed = x
@@ -353,7 +363,8 @@ class XTgtSpeedMixin(SmoothPointGetter):
y = applyDamage(
dmgMap=commonData['dmgMap'],
applicationMap=applicationMap,
tgtResists=commonData['tgtResists']).total
tgtResists=commonData['tgtResists'],
tgtFullHp=commonData['tgtFullHp']).total
return y
@@ -398,7 +409,8 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
'tgtSpeed': tgtSpeed,
'tgtSigMult': tgtSigMult,
'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']),
'tgtResists': tgt.getResists()}
'tgtResists': tgt.getResists(),
'tgtFullHp': tgt.getFullHp()}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
tgtSigRadius = x
@@ -414,7 +426,8 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
y = applyDamage(
dmgMap=commonData['dmgMap'],
applicationMap=applicationMap,
tgtResists=commonData['tgtResists']).total
tgtResists=commonData['tgtResists'],
tgtFullHp=commonData['tgtFullHp']).total
return y

View File

@@ -89,7 +89,7 @@ class FitDamageStatsGraph(FitGraph):
cols = []
if not GraphSettings.getInstance().get('ignoreResists'):
cols.append('Target Resists')
cols.extend(('Speed', 'SigRadius', 'Radius'))
cols.extend(('Speed', 'SigRadius', 'Radius', 'FullHP'))
return cols
# Calculation stuff

View File

@@ -145,6 +145,11 @@ class TargetWrapper(BaseWrapper):
else:
return em, therm, kin, explo
def getFullHp(self):
if self.isProfile:
return self.item.hp
if self.isFit:
return self.item.hp.get('shield', 0) + self.item.hp.get('armor', 0) + self.item.hp.get('hull', 0)
def _getShieldResists(ship):

View File

@@ -195,7 +195,11 @@ class DroneView(Display):
@staticmethod
def droneKey(drone):
groupName = Market.getInstance().getMarketGroupByItem(drone.item).marketGroupName
if drone.isMutated:
item = drone.baseItem
else:
item = drone.item
groupName = Market.getInstance().getMarketGroupByItem(item).marketGroupName
return (DRONE_ORDER.index(groupName), drone.isMutated, drone.fullName)
def fitChanged(self, event):

View File

@@ -34,7 +34,10 @@ from service.fit import Fit
from service.market import Market
FIGHTER_ORDER = ('Light Fighter', 'Heavy Fighter', 'Support Fighter')
FIGHTER_ORDER = (
'Light Fighter', 'Structure Light Fighter',
'Heavy Fighter', 'Structure Heavy Fighter',
'Support Fighter', 'Structure Support Fighter')
_t = wx.GetTranslation

View File

@@ -20,6 +20,7 @@ from gui.builtinContextMenus.targetProfile import editor
from gui.builtinContextMenus import itemStats
from gui.builtinContextMenus import itemMarketJump
from gui.builtinContextMenus import fitSystemSecurity # Not really an item info but want to keep it here
from gui.builtinContextMenus import fitPilotSecurity # Not really an item info but want to keep it here
from gui.builtinContextMenus import shipJump
# Generic item manipulations
from gui.builtinContextMenus import itemRemove

View File

@@ -25,7 +25,7 @@ class AddToCargoAmmo(ContextMenuSingle):
return True
def getText(self, callingWindow, itmContext, mainItem):
if mainItem.marketGroup.name == "Scan Probes":
if mainItem.marketGroup and mainItem.marketGroup.name == "Scan Probes":
return _t("Add {0} to Cargo (x8)").format(itmContext)
return _t("Add {0} to Cargo (x1000)").format(itmContext)
@@ -34,7 +34,7 @@ class AddToCargoAmmo(ContextMenuSingle):
fitID = self.mainFrame.getActiveFit()
typeID = int(mainItem.ID)
if mainItem.marketGroup.name == "Scan Probes":
if mainItem.marketGroup and mainItem.marketGroup.name == "Scan Probes":
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=8)
else:
command = cmd.GuiAddCargoCommand(fitID=fitID, itemID=typeID, amount=1000)

View File

@@ -0,0 +1,157 @@
import re
import wx
import gui.fitCommands as cmd
import gui.mainFrame
from gui.contextMenu import ContextMenuUnconditional
from service.fit import Fit
_t = wx.GetTranslation
class FitPilotSecurityMenu(ContextMenuUnconditional):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, callingWindow, srcContext):
if srcContext != "fittingShip":
return False
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
if fit.ship.name not in ('Pacifier', 'Enforcer', 'Marshal', 'Sidewinder', 'Cobra', 'Python'):
return
return True
def getText(self, callingWindow, itmContext):
return _t("Pilot Security Status")
def addOption(self, menu, optionLabel, optionValue):
id = ContextMenuUnconditional.nextID()
self.optionIds[id] = optionValue
menuItem = wx.MenuItem(menu, id, optionLabel, kind=wx.ITEM_CHECK)
menu.Bind(wx.EVT_MENU, self.handleMode, menuItem)
return menuItem
def addOptionCustom(self, menu, optionLabel):
id = ContextMenuUnconditional.nextID()
menuItem = wx.MenuItem(menu, id, optionLabel, kind=wx.ITEM_CHECK)
menu.Bind(wx.EVT_MENU, self.handleModeCustom, menuItem)
return menuItem
def getSubMenu(self, callingWindow, context, rootMenu, i, pitem):
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
msw = True if "wxMSW" in wx.PlatformInfo else False
self.optionIds = {}
sub = wx.Menu()
presets = (-10, -8, -6, -4, -2, 0, 1, 2, 3, 4, 5)
# Inherit
char_sec_status = round(fit.character.secStatus, 2)
menuItem = self.addOption(rootMenu if msw else sub, _t('Character') + f' ({char_sec_status})', None)
sub.Append(menuItem)
menuItem.Check(fit.pilotSecurity is None)
# Custom
label = _t('Custom')
is_checked = False
if fit.pilotSecurity is not None and fit.pilotSecurity not in presets:
sec_status = round(fit.getPilotSecurity(), 2)
label += f' ({sec_status})'
is_checked = True
menuItem = self.addOptionCustom(rootMenu if msw else sub, label)
sub.Append(menuItem)
menuItem.Check(is_checked)
sub.AppendSeparator()
# Predefined options
for sec_status in presets:
menuItem = self.addOption(rootMenu if msw else sub, str(sec_status), sec_status)
sub.Append(menuItem)
menuItem.Check(fit.pilotSecurity == sec_status)
return sub
def handleMode(self, event):
optionValue = self.optionIds[event.Id]
self.mainFrame.command.Submit(cmd.GuiChangeFitPilotSecurityCommand(
fitID=self.mainFrame.getActiveFit(),
secStatus=optionValue))
def handleModeCustom(self, event):
fitID = self.mainFrame.getActiveFit()
fit = Fit.getInstance().getFit(fitID)
sec_status = fit.getPilotSecurity()
with SecStatusChanger(self.mainFrame, value=sec_status) as dlg:
if dlg.ShowModal() == wx.ID_OK:
cleanInput = re.sub(r'[^0-9.\-+]', '', dlg.input.GetLineText(0).strip())
if cleanInput:
try:
cleanInputFloat = float(cleanInput)
except ValueError:
return
else:
return
self.mainFrame.command.Submit(cmd.GuiChangeFitPilotSecurityCommand(
fitID=fitID, secStatus=max(-10.0, min(5.0, cleanInputFloat))))
FitPilotSecurityMenu.register()
class SecStatusChanger(wx.Dialog):
def __init__(self, parent, value):
super().__init__(parent, title=_t('Change Security Status'), style=wx.DEFAULT_DIALOG_STYLE)
self.SetMinSize((346, 156))
bSizer1 = wx.BoxSizer(wx.VERTICAL)
bSizer2 = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, _t('Security Status (min -10.0, max 5.0):'))
bSizer2.Add(text, 0)
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
if value is None:
value = '0.0'
else:
if value == int(value):
value = int(value)
value = str(value)
self.input.SetValue(value)
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.Fit()
self.CenterOnParent()
self.input.SetFocus()
self.input.SelectAll()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)
# checks to make sure it's valid number
@staticmethod
def onChar(event):
key = event.GetKeyCode()
acceptable_characters = '1234567890.-+'
acceptable_keycode = [3, 22, 13, 8, 127] # modifiers like delete, copy, paste
if key in acceptable_keycode or key >= 255 or (key < 255 and chr(key) in acceptable_characters):
event.Skip()
return
else:
return False

View File

@@ -13,6 +13,16 @@ from service.fit import Fit
_t = wx.GetTranslation
GLORIFIED_PREFIX = 'Gl. '
def nameSorter(mutaplasmid):
name = mutaplasmid.shortName
if name.startswith(GLORIFIED_PREFIX):
return name[len(GLORIFIED_PREFIX):], True
return name, False
class ChangeItemMutation(ContextMenuSingle):
def __init__(self):
@@ -45,7 +55,7 @@ class ChangeItemMutation(ContextMenuSingle):
menu = rootMenu if msw else sub
for mutaplasmid in mainItem.item.mutaplasmids:
for mutaplasmid in sorted(mainItem.item.mutaplasmids, key=nameSorter):
id = ContextMenuSingle.nextID()
self.eventIDs[id] = (mutaplasmid, mainItem)
mItem = wx.MenuItem(menu, id, mutaplasmid.shortName)

View File

@@ -72,6 +72,7 @@ AttrGroupDict = {
"specialAmmoHoldCapacity",
"specialCommandCenterHoldCapacity",
"specialPlanetaryCommoditiesHoldCapacity",
"specialColonyResourcesHoldCapacity",
"structureDamageLimit",
"specialSubsystemHoldCapacity",
"emDamageResonance",

View File

@@ -5,3 +5,5 @@ import wx.lib.newevent
ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent()
RECENTLY_USED_MODULES = -2
CHARGES_FOR_FIT = -3

View File

@@ -2,15 +2,17 @@ import wx
from logbook import Logger
import gui.builtinMarketBrowser.pfSearchBox as SBox
import gui.globalEvents as GE
from config import slotColourMap, slotColourMapDark
from eos.saveddata.module import Module
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES
from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT
from gui.contextMenu import ContextMenu
from gui.display import Display
from gui.utils.staticHelpers import DragDropHelper
from gui.utils.dark import isDark
from service.fit import Fit
from service.market import Market
from service.ammo import Ammo
pyfalog = Logger(__name__)
@@ -32,6 +34,7 @@ class ItemView(Display):
self.filteredStore = set()
self.sMkt = marketBrowser.sMkt
self.sFit = Fit.getInstance()
self.sAmmo = Ammo.getInstance()
self.marketBrowser = marketBrowser
self.marketView = marketBrowser.marketView
@@ -51,6 +54,9 @@ class ItemView(Display):
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag)
# the "charges for active fitting" needs to listen to fitting changes
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
self.active = []
def delaySearch(self, evt):
@@ -91,7 +97,11 @@ class ItemView(Display):
if sel.IsOk():
# Get data field of the selected item (which is a marketGroup ID if anything was selected)
seldata = self.marketView.GetItemData(sel)
if seldata is not None and seldata != RECENTLY_USED_MODULES:
if seldata == RECENTLY_USED_MODULES:
items = self.sMkt.getRecentlyUsed()
elif seldata == CHARGES_FOR_FIT:
items = self.getChargesForActiveFit()
elif seldata is not None:
# If market group treeview item doesn't have children (other market groups or dummies),
# then it should have items in it and we want to request them
if self.marketView.ItemHasChildren(sel) is False:
@@ -103,11 +113,7 @@ class ItemView(Display):
else:
items = set()
else:
# If method was called but selection wasn't actually made or we have a hit on recently used modules
if seldata == RECENTLY_USED_MODULES:
items = self.sMkt.getRecentlyUsed()
else:
items = set()
items = set()
# Fill store
self.updateItemStore(items)
@@ -115,6 +121,9 @@ class ItemView(Display):
# Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered)
if seldata == RECENTLY_USED_MODULES:
self.marketBrowser.mode = 'recent'
if seldata == CHARGES_FOR_FIT:
self.marketBrowser.mode = 'charges'
self.setToggles()
if context == 'tree' and self.marketBrowser.settings.get('marketMGMarketSelectMode') == 1:
@@ -123,6 +132,41 @@ class ItemView(Display):
btn.setUserSelection(True)
self.filterItemStore()
def getChargesForActiveFit(self):
fitId = self.mainFrame.getActiveFit()
# no active fit => no charges
if fitId is None:
return set()
fit = self.sFit.getFit(fitId)
# use a set so we only add one entry for each charge
items = set()
for mod in fit.modules:
charges = self.sAmmo.getModuleFlatAmmo(mod)
for charge in charges:
items.add(charge)
return items
def fitChanged(self, event):
# skip the event so the other handlers also get called
event.Skip()
if self.marketBrowser.mode != 'charges':
return
activeFitID = self.mainFrame.getActiveFit()
# if it was not the active fitting that was changed, do not do anything
if activeFitID is not None and activeFitID not in event.fitIDs:
return
items = self.getChargesForActiveFit()
# update the UI
self.updateItemStore(items)
self.filterItemStore()
def updateItemStore(self, items):
self.unfilteredStore = items

View File

@@ -1,7 +1,7 @@
import wx
from gui.cachingImageList import CachingImageList
from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES
from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES, CHARGES_FOR_FIT
from logbook import Logger
@@ -35,6 +35,9 @@ class MarketTree(wx.TreeCtrl):
# Add recently used modules node
rumIconId = self.addImage("market_small", "gui")
self.AppendItem(self.root, _t("Recently Used Items"), rumIconId, data=RECENTLY_USED_MODULES)
# Add charges for active fitting node
cffIconId = self.addImage("damagePattern_small", "gui")
self.AppendItem(self.root, _t("Charges For Active Fit"), cffIconId, data=CHARGES_FOR_FIT)
# Bind our lookup method to when the tree gets expanded
self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)

View File

@@ -173,7 +173,7 @@ class FirepowerViewFull(StatsView):
if hasSpool:
lines.append("")
lines.append(_t("Current") + ": {}".format(formatAmount(normal.total, prec, lowest, highest)))
for dmgType in normal.names():
for dmgType in normal.names(includePure=True):
val = getattr(normal, dmgType, None)
if val:
lines.append("{}{}: {}%".format(
@@ -215,13 +215,13 @@ class FirepowerViewFull(StatsView):
val = val() if fit is not None else None
preSpoolVal = preSpoolVal() if fit is not None else None
fullSpoolVal = fullSpoolVal() if fit is not None else None
if self._cachedValues[counter] != val:
if self._cachedValues[counter] != getattr(val, 'total', None):
tooltipText = dpsToolTip(val, preSpoolVal, fullSpoolVal, prec, lowest, highest)
label.SetLabel(valueFormat.format(
formatAmount(0 if val is None else val.total, prec, lowest, highest),
"\u02e2" if hasSpoolUp(preSpoolVal, fullSpoolVal) else ""))
label.SetToolTip(wx.ToolTip(tooltipText))
self._cachedValues[counter] = val
self._cachedValues[counter] = getattr(val, 'total', None)
counter += 1
self.panel.Layout()

View File

@@ -112,6 +112,7 @@ class TargetingMiscViewMinimal(StatsView):
cargoNamesOrder = OrderedDict((
("fleetHangarCapacity", _t("Fleet hangar")),
("shipMaintenanceBayCapacity", _t("Maintenance bay")),
("specialColonyResourcesHoldCapacity", _t("Infrastructure hold")),
("specialAmmoHoldCapacity", _t("Ammo hold")),
("specialFuelBayCapacity", _t("Fuel bay")),
("specialShipHoldCapacity", _t("Ship hold")),
@@ -134,6 +135,7 @@ class TargetingMiscViewMinimal(StatsView):
cargoValues = {
"main": lambda: fit.ship.getModifiedItemAttr("capacity"),
"fleetHangarCapacity": lambda: fit.ship.getModifiedItemAttr("fleetHangarCapacity"),
"specialColonyResourcesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialColonyResourcesHoldCapacity"),
"shipMaintenanceBayCapacity": lambda: fit.ship.getModifiedItemAttr("shipMaintenanceBayCapacity"),
"specialAmmoHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialAmmoHoldCapacity"),
"specialFuelBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialFuelBayCapacity"),

View File

@@ -197,6 +197,30 @@ class SignatureRadiusColumn(GraphColumn):
SignatureRadiusColumn.register()
class FullHpColumn(GraphColumn):
name = 'FullHP'
stickPrefixToValue = True
def __init__(self, fittingView, params):
super().__init__(fittingView, 68)
def _getValue(self, stuff):
if isinstance(stuff, Fit):
full_hp = stuff.hp.get('shield', 0) + stuff.hp.get('armor', 0) + stuff.hp.get('hull', 0)
elif isinstance(stuff, TargetProfile):
full_hp = stuff.hp
else:
full_hp = 0
return full_hp, 'hp'
def _getFitTooltip(self):
return 'Total raw HP'
FullHpColumn.register()
class ShieldAmountColumn(GraphColumn):
name = 'ShieldAmount'

View File

@@ -93,8 +93,6 @@ class Miscellanea(ViewColumn):
text = "{} dmg".format(formatAmount(dmg, 3, 0, 6))
tooltip = "Raw damage done"
return text, tooltip
pass
elif itemGroup in ("Energy Weapon", "Hybrid Weapon", "Projectile Weapon", "Combat Drone", "Fighter Drone"):
trackingSpeed = stuff.getModifiedItemAttr("trackingSpeed")
optimalSig = stuff.getModifiedItemAttr("optimalSigRadius")
@@ -590,7 +588,7 @@ class Miscellanea(ViewColumn):
text = "{0}/s".format(formatAmount(capPerSec, 3, 0, 3))
tooltip = "Energy neutralization per second"
return text, tooltip
elif itemGroup in ("Micro Jump Drive", "Micro Jump Field Generators"):
elif itemGroup in ("Micro Jump Drive", "Micro Jump Field Generators", "Capital Mobility Modules"):
cycleTime = stuff.getModifiedItemAttr("duration") / 1000
text = "{0}s".format(formatAmount(cycleTime, 3, 0, 3))
tooltip = "Spoolup time"
@@ -810,6 +808,19 @@ class Miscellanea(ViewColumn):
text = "{}".format(formatAmount(scanStr, 4, 0, 3))
tooltip = "Scan strength at {} AU scan range".format(formatAmount(baseRange, 3, 0, 0))
return text, tooltip
elif chargeGroup in ("SCARAB Breacher Pods",):
duration = stuff.getModifiedChargeAttr("dotDuration") / 1000
dmgAbs = stuff.getModifiedChargeAttr("dotMaxDamagePerTick")
dmgRel = stuff.getModifiedChargeAttr("dotMaxHPPercentagePerTick")
text = "{}/{}% over {}s".format(
formatAmount(dmgAbs * duration, 3, 0, 6),
formatAmount(dmgRel * duration, 3, 0, 6),
formatAmount(duration, 0, 0, 0))
fullDmgHp = dmgAbs / (dmgRel / 100)
tooltip = (
'Pure damage inflicted over time, minimum of absolute / relative\n'
'Full DPS from {} target HP').format(formatAmount(fullDmgHp, 3, 0, 6))
return text, tooltip
else:
return "", None
else:

View File

@@ -115,7 +115,7 @@ class CharacterEntityEditor(EntityEditor):
sChar = Character.getInstance()
if entity.alphaCloneID:
trimmed_name = re.sub('[ \(\u03B1\)]+$', '', name)
trimmed_name = re.sub('[ \\(\u03B1\\)]+$', '', name)
sChar.rename(entity, trimmed_name)
else:
sChar.rename(entity, name)
@@ -918,7 +918,7 @@ class SecStatusDialog(wx.Dialog):
self.m_staticText1.Wrap(-1)
bSizer1.Add(self.m_staticText1, 1, wx.ALL | wx.EXPAND, 5)
self.floatSpin = FloatSpin(self, value=sec, min_val=-5.0, max_val=5.0, increment=0.1, digits=2, size=(-1, -1))
self.floatSpin = FloatSpin(self, value=sec, min_val=-10.0, max_val=5.0, increment=0.1, digits=2, size=(-1, -1))
bSizer1.Add(self.floatSpin, 0, wx.ALIGN_CENTER | wx.ALL, 5)
btnOk = wx.Button(self, wx.ID_OK)

View File

@@ -112,7 +112,7 @@ class FitBrowserLiteDialog(wx.Dialog):
return True
matches = []
searchTokens = [t.lower() for t in re.split('\s+', searchPattern)]
searchTokens = [t.lower() for t in re.split(r'\s+', searchPattern)]
for fit in self.allFits:
if isMatch(fit, searchTokens):
matches.append(fit)

View File

@@ -12,6 +12,7 @@ from .gui.cargo.remove import GuiRemoveCargosCommand
from .gui.commandFit.add import GuiAddCommandFitsCommand
from .gui.commandFit.remove import GuiRemoveCommandFitsCommand
from .gui.commandFit.toggleStates import GuiToggleCommandFitStatesCommand
from .gui.fitPilotSecurity import GuiChangeFitPilotSecurityCommand
from .gui.fitRename import GuiRenameFitCommand
from .gui.fitRestrictionToggle import GuiToggleFittingRestrictionsCommand
from .gui.fitSystemSecurity import GuiChangeFitSystemSecurityCommand

View File

@@ -0,0 +1,32 @@
import wx
from logbook import Logger
from service.fit import Fit
pyfalog = Logger(__name__)
class CalcChangeFitPilotSecurityCommand(wx.Command):
def __init__(self, fitID, secStatus):
wx.Command.__init__(self, True, 'Change Fit Pilot Security')
self.fitID = fitID
self.secStatus = secStatus
self.savedSecStatus = None
def Do(self):
pyfalog.debug('Doing changing pilot security status of fit {} to {}'.format(self.fitID, self.secStatus))
fit = Fit.getInstance().getFit(self.fitID, basic=True)
# Fetching status via getter and then saving 'raw' security status
# is intentional, to restore pre-change state properly
if fit.pilotSecurity == self.secStatus:
return False
self.savedSecStatus = fit.pilotSecurity
fit.pilotSecurity = self.secStatus
return True
def Undo(self):
pyfalog.debug('Undoing changing pilot security status of fit {} to {}'.format(self.fitID, self.secStatus))
cmd = CalcChangeFitPilotSecurityCommand(fitID=self.fitID, secStatus=self.savedSecStatus)
return cmd.Do()

View File

@@ -0,0 +1,36 @@
import wx
from service.fit import Fit
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from gui.fitCommands.helpers import InternalCommandHistory
from gui.fitCommands.calc.fitPilotSecurity import CalcChangeFitPilotSecurityCommand
class GuiChangeFitPilotSecurityCommand(wx.Command):
def __init__(self, fitID, secStatus):
wx.Command.__init__(self, True, 'Change Fit Pilot Security')
self.internalHistory = InternalCommandHistory()
self.fitID = fitID
self.secStatus = secStatus
def Do(self):
cmd = CalcChangeFitPilotSecurityCommand(fitID=self.fitID, secStatus=self.secStatus)
success = self.internalHistory.submit(cmd)
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success
def Undo(self):
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
return success

View File

@@ -146,7 +146,7 @@ class MarketBrowser(wx.Panel):
setting = self.settings.get('marketMGSearchMode')
# We turn on all meta buttons for the duration of search/recents
if setting == 1:
if newMode in ('search', 'recent'):
if newMode in ('search', 'recent', 'charges'):
for btn in self.metaButtons:
btn.setUserSelection(True)
if newMode == 'normal':

View File

@@ -191,7 +191,7 @@ class ShipBrowser(wx.Panel):
"amarr", "caldari", "gallente", "minmatar",
"sisters", "ore", "concord",
"serpentis", "angel", "blood", "sansha", "guristas", "mordu",
"jove", "triglavian", "upwell", None
"deathless", "jove", "triglavian", "upwell", None
]
def raceNameKey(self, ship):

View File

@@ -123,13 +123,14 @@ class TargetProfileEditor(AuxiliaryFrame):
ATTRIBUTES = OrderedDict([
('maxVelocity', (_t('Maximum speed'), 'm/s')),
('signatureRadius', (_t('Signature radius\nLeave blank for infinitely big value'), 'm')),
('radius', (_t('Radius'), 'm'))])
('radius', (_t('Radius\nThe radius of the sphere that represents a ship/drone in space. Affects range calculations.'), 'm')),
('hp', (_t('Total HP\nAffects how much damage breacher pods can do. Leave blank for infinitely big value'), 'hp'))])
def __init__(self, parent):
super().__init__(
parent, id=wx.ID_ANY, title=_t("Target Profile Editor"), resizeable=True,
# Dropdown list widget is scaled to its longest content line on GTK, adapt to that
size=wx.Size(500, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(350, 240))
size=wx.Size(630, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(450, 240))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.block = False
@@ -145,36 +146,29 @@ class TargetProfileEditor(AuxiliaryFrame):
contentSizer = wx.BoxSizer(wx.VERTICAL)
resistEditSizer = wx.FlexGridSizer(2, 6, 0, 2)
resistEditSizer.AddGrowableCol(0)
resistEditSizer.AddGrowableCol(5)
resistEditSizer.SetFlexibleDirection(wx.BOTH)
resistEditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
resistEditSizer = wx.BoxSizer(wx.HORIZONTAL)
resistEditSizer.AddStretchSpacer()
defSize = wx.Size(50, -1)
defSize = wx.Size(70, -1)
for i, type_ in enumerate(self.DAMAGE_TYPES):
if i % 2:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
border = 25
else:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
border = 5
for type_ in self.DAMAGE_TYPES:
leftPad = 25 if type_ != list(self.DAMAGE_TYPES)[0] else 0
ttText = self.DAMAGE_TYPES[type_]
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big" % type_, "gui"))
bmp.SetToolTip(wx.ToolTip(ttText))
resistEditSizer.Add(bmp, 0, style, border)
resistEditSizer.Add(bmp, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, leftPad)
# set text edit
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize, validator=ResistValidator())
editBox = FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)
editBox.SetToolTip(wx.ToolTip(ttText))
self.Bind(event=wx.EVT_TEXT, handler=self.OnFieldChanged, source=editBox)
setattr(self, '{}Edit'.format(type_), editBox)
resistEditSizer.Add(editBox, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
resistEditSizer.Add(editBox, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
unit = wx.StaticText(self, wx.ID_ANY, "%", wx.DefaultPosition, wx.DefaultSize, 0)
unit.SetToolTip(wx.ToolTip(ttText))
resistEditSizer.Add(unit, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
contentSizer.Add(resistEditSizer, 0, wx.EXPAND | wx.ALL, 5)
resistEditSizer.AddStretchSpacer()
contentSizer.Add(resistEditSizer, 1, wx.EXPAND | wx.ALL, 5)
miscAttrSizer = wx.BoxSizer(wx.HORIZONTAL)
miscAttrSizer.AddStretchSpacer()

BIN
imgs/gui/hp_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
imgs/icons/10155@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

BIN
imgs/icons/10155@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/icons/10848@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

BIN
imgs/icons/10848@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 B

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

BIN
imgs/icons/25240@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

BIN
imgs/icons/25240@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/icons/25241@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

BIN
imgs/icons/25241@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/25242@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

BIN
imgs/icons/25242@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/25243@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B

BIN
imgs/icons/25243@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/25250@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

BIN
imgs/icons/25250@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
imgs/icons/25251@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
imgs/icons/25251@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/25252@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

BIN
imgs/icons/25252@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/25253@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

BIN
imgs/icons/25253@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 605 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

BIN
imgs/icons/26004@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

BIN
imgs/icons/26004@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
imgs/icons/26005@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

BIN
imgs/icons/26005@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
imgs/icons/26016@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

BIN
imgs/icons/26016@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/26017@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

BIN
imgs/icons/26017@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/26018@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

BIN
imgs/icons/26018@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/26019@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

BIN
imgs/icons/26019@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/26038@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

BIN
imgs/icons/26038@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
imgs/icons/26039@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

BIN
imgs/icons/26039@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
imgs/icons/26040@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 B

BIN
imgs/icons/26040@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
imgs/icons/26041@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 B

BIN
imgs/icons/26041@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
imgs/icons/26042@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

Some files were not shown because too many files have changed in this diff Show More