Skip to content

Commit 2a0c78d

Browse files
authored
Merge pull request #2779 from GET-R3AL/Application-Profile-Fix
Replace Damage Envelope graph with Application Profile graph
2 parents 377c262 + d0adf06 commit 2a0c78d

22 files changed

Lines changed: 4414 additions & 497 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#Pyfa file
1414
pyfaFits.html
1515

16+
#Local EVE static data dump
17+
eve.db
18+
1619
#Temporary files
1720
*.py__jb_tmp__
1821

graphs/data/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
from . import fitDamageStats
22-
from . import fitDamageEnvelope
22+
from . import fitApplicationProfile
2323
from . import fitEwarStats
2424
from . import fitRemoteReps
2525
from . import fitShieldRegen

graphs/data/fitDamageEnvelope/__init__.py renamed to graphs/data/fitApplicationProfile/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
1818
# =============================================================================
1919

20+
from .graph import FitAmmoOptimalDpsGraph
2021

21-
from .graph import FitDamageEnvelopeGraph
2222

23-
FitDamageEnvelopeGraph.register()
23+
FitAmmoOptimalDpsGraph.register()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# =============================================================================
2+
# Copyright (C) 2010 Diego Duclos
3+
#
4+
# This file is part of pyfa.
5+
#
6+
# pyfa is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# pyfa is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
18+
# =============================================================================
19+
20+
# Import key functions for convenient access
21+
from .projected import (
22+
buildProjectedCache,
23+
getProjectedParamsAtDistance,
24+
)
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# =============================================================================
2+
# Copyright (C) 2010 Diego Duclos
3+
#
4+
# This file is part of pyfa.
5+
#
6+
# pyfa is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# pyfa is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
18+
# =============================================================================
19+
20+
# =============================================================================
21+
# Constants
22+
# =============================================================================
23+
24+
# Navy faction ammo prefixes (for S/M/L ammo)
25+
NAVY_PREFIXES = (
26+
'Imperial Navy ',
27+
'Republic Fleet ',
28+
'Caldari Navy ',
29+
'Federation Navy ',
30+
)
31+
32+
# Capital (XL) "navy-tier" faction ammo prefixes
33+
# There is no empire Navy XL ammo, so pirate faction serves as the "navy" tier for capitals
34+
CAPITAL_NAVY_PREFIXES = (
35+
'Sansha ',
36+
'Arch Angel ',
37+
'Shadow ',
38+
)
39+
40+
41+
# =============================================================================
42+
# Quality Tier Filtering
43+
# =============================================================================
44+
45+
def filterChargesByQuality(charges, qualityTier):
46+
"""
47+
Filter charges based on quality tier selection.
48+
49+
Args:
50+
charges: List of charge items
51+
qualityTier: 't1', 'navy', or 'all'
52+
53+
Returns:
54+
Filtered list of charges
55+
56+
Tiers are cumulative (each tier includes everything below it):
57+
- 't1': Tech I only (metaGroup 1)
58+
- 'navy': t1 + Tech II (metaGroup 2) + Navy faction ammo (Imperial Navy,
59+
Republic Fleet, Caldari Navy, Federation Navy)
60+
For XL (capital) ammo: includes pirate faction (Sansha, Arch Angel, Shadow)
61+
- 'all': Everything including high-tier faction (Blood, Dark Blood, True Sansha, etc.)
62+
63+
Charges with no meta group in the game data (metaGroupID is NULL - e.g. all
64+
Baryon Exotic Plasma and every XL Triglavian charge) are treated as Tech I.
65+
Otherwise they would be filtered out of every tier despite being basic ammo.
66+
"""
67+
if qualityTier == 'all':
68+
return charges
69+
70+
filtered = []
71+
classifiable = False # Did any charge have a meta group we could rank?
72+
for charge in charges:
73+
mg = charge.metaGroup
74+
mgId = mg.ID if mg else None
75+
if mgId is not None:
76+
classifiable = True
77+
78+
# Tech I (metaGroup 1), or unclassified ammo (NULL metaGroup) treated as
79+
# Tech I - always included in every tier.
80+
if mgId == 1 or mgId is None:
81+
filtered.append(charge)
82+
continue
83+
84+
# 'navy' tier additionally includes Tech II and Navy faction ammo.
85+
if qualityTier == 'navy':
86+
# Tech II (metaGroup 2) - distinct ammo type like Conflagration, Void, etc.
87+
if mgId == 2:
88+
filtered.append(charge)
89+
continue
90+
91+
# Navy faction ammo (metaGroup 4)
92+
if mgId == 4:
93+
# Check if it's XL (capital) ammo by name suffix
94+
isCapital = charge.name.endswith(' XL')
95+
96+
if isCapital:
97+
# For capital ammo, use pirate faction prefixes as "navy" tier
98+
if any(charge.name.startswith(prefix) for prefix in CAPITAL_NAVY_PREFIXES):
99+
filtered.append(charge)
100+
else:
101+
# For subcap ammo, use empire Navy prefixes
102+
if any(charge.name.startswith(prefix) for prefix in NAVY_PREFIXES):
103+
filtered.append(charge)
104+
105+
# Honor the user's tier selection even when it excludes every charge (the
106+
# weapon simply has no ammo in this tier). Only fall back to the full list
107+
# when no charge could be classified by meta group at all - in that case
108+
# the tier system does not apply and returning nothing would wrongly hide
109+
# the weapon.
110+
if filtered or classifiable:
111+
return filtered
112+
return charges
113+
114+
115+
# =============================================================================
116+
# Charge Stats Extraction
117+
# =============================================================================
118+
119+
def getChargeStats(charge):
120+
"""
121+
Extract charge stats including damage values and multipliers.
122+
123+
Args:
124+
charge: The charge item
125+
126+
Returns:
127+
Dict with damage values and range/falloff/tracking multipliers
128+
"""
129+
em = charge.getAttribute('emDamage') or 0
130+
thermal = charge.getAttribute('thermalDamage') or 0
131+
kinetic = charge.getAttribute('kineticDamage') or 0
132+
explosive = charge.getAttribute('explosiveDamage') or 0
133+
134+
return {
135+
'emDamage': em,
136+
'thermalDamage': thermal,
137+
'kineticDamage': kinetic,
138+
'explosiveDamage': explosive,
139+
'totalDamage': em + thermal + kinetic + explosive,
140+
'rangeMultiplier': charge.getAttribute('weaponRangeMultiplier') or 1,
141+
'falloffMultiplier': charge.getAttribute('fallofMultiplier') or 1,
142+
'trackingMultiplier': charge.getAttribute('trackingSpeedMultiplier') or 1
143+
}
144+
145+
146+
# =============================================================================
147+
# Resist Application
148+
# =============================================================================
149+
150+
def applyResists(chargeStats, tgtResists):
151+
"""
152+
Apply target resists to charge stats.
153+
154+
Args:
155+
chargeStats: Dict from getChargeStats
156+
tgtResists: Tuple of (em, therm, kin, explo) resist values (0-1)
157+
158+
Returns:
159+
New dict with resisted damage values
160+
"""
161+
if not tgtResists:
162+
return chargeStats
163+
164+
emRes, thermRes, kinRes, exploRes = tgtResists
165+
166+
em = chargeStats['emDamage'] * (1 - emRes)
167+
thermal = chargeStats['thermalDamage'] * (1 - thermRes)
168+
kinetic = chargeStats['kineticDamage'] * (1 - kinRes)
169+
explosive = chargeStats['explosiveDamage'] * (1 - exploRes)
170+
171+
result = chargeStats.copy()
172+
result.update({
173+
'emDamage': em,
174+
'thermalDamage': thermal,
175+
'kineticDamage': kinetic,
176+
'explosiveDamage': explosive,
177+
'totalDamage': em + thermal + kinetic + explosive
178+
})
179+
return result
180+
181+
182+
# =============================================================================
183+
# Charge Data Precomputation
184+
# =============================================================================
185+
186+
def precomputeChargeData(turretBase, charges, skillMult=1.0, tgtResists=None):
187+
"""
188+
Pre-compute constant values for each charge.
189+
190+
This computes effective stats (turret base * charge multipliers) and
191+
raw volley for each charge, which can then be used for fast lookups.
192+
193+
Args:
194+
turretBase: Base turret stats dict from getTurretBaseStats
195+
charges: List of charge items
196+
skillMult: Skill damage multiplier from getSkillMultiplier
197+
tgtResists: Target resists tuple or None
198+
199+
Returns:
200+
List of dicts with: name, raw_volley, effective_optimal,
201+
effective_falloff, effective_tracking
202+
203+
Note: We do NOT store raw_dps - it's derived from raw_volley / cycle_time
204+
when needed at the mixin level.
205+
"""
206+
chargeData = []
207+
208+
for charge in charges:
209+
stats = getChargeStats(charge)
210+
211+
# Apply resists early for efficiency
212+
if tgtResists:
213+
stats = applyResists(stats, tgtResists)
214+
215+
# Compute effective turret stats with charge modifiers
216+
effectiveOptimal = turretBase['optimal'] * stats['rangeMultiplier']
217+
effectiveFalloff = turretBase['falloff'] * stats['falloffMultiplier']
218+
effectiveTracking = turretBase['tracking'] * stats['trackingMultiplier']
219+
220+
# Compute raw volley (unmodified by range/tracking)
221+
rawVolley = stats['totalDamage'] * skillMult * turretBase['damageMultiplier']
222+
223+
chargeData.append({
224+
'name': charge.name,
225+
'raw_volley': rawVolley,
226+
'effective_optimal': effectiveOptimal,
227+
'effective_falloff': effectiveFalloff,
228+
'effective_tracking': effectiveTracking
229+
})
230+
231+
return chargeData
232+
233+
234+
def getLongestRangeMultiplier(charges):
235+
"""
236+
Get the maximum range multiplier from a list of charges.
237+
238+
Used to calculate the max effective range of a turret for cache sizing.
239+
240+
Args:
241+
charges: List of charge items
242+
243+
Returns:
244+
The highest rangeMultiplier value among all charges
245+
"""
246+
if not charges:
247+
return 1.0
248+
249+
maxRangeMult = 1.0
250+
for charge in charges:
251+
rangeMult = charge.getAttribute('weaponRangeMultiplier') or 1.0
252+
if rangeMult > maxRangeMult:
253+
maxRangeMult = rangeMult
254+
255+
return maxRangeMult

0 commit comments

Comments
 (0)