Add web strength vs range graph
This commit is contained in:
62
graphs/calc.py
Normal file
62
graphs/calc.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
if distance is None:
|
||||
return 1
|
||||
if srcFalloffRange > 0:
|
||||
return 0.5 ** ((max(0, distance - srcOptimalRange) / srcFalloffRange) ** 2)
|
||||
elif distance <= srcOptimalRange:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||
# as multipliers arrive in different form) in here to not make actual attribute
|
||||
# calculations slower than they already are due to extra function calls
|
||||
def calculateMultiplier(multipliers):
|
||||
"""
|
||||
multipliers: dictionary in format:
|
||||
{stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
|
||||
"""
|
||||
val = 1
|
||||
for penalizedMultipliers in multipliers.values():
|
||||
# A quick explanation of how this works:
|
||||
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
||||
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
||||
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
||||
# 2: The most significant bonuses take the smallest penalty,
|
||||
# This means we'll have to sort
|
||||
abssort = lambda _val: -abs(_val - 1)
|
||||
l1.sort(key=abssort)
|
||||
l2.sort(key=abssort)
|
||||
# 3: The first module doesn't get penalized at all
|
||||
# Any module after the first takes penalties according to:
|
||||
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
||||
for l in (l1, l2):
|
||||
for i in range(len(l)):
|
||||
bonus = l[i]
|
||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||
return val
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
from . import fitDamageStats
|
||||
from . import fitEwarStats
|
||||
from . import fitShieldRegen
|
||||
from . import fitCapRegen
|
||||
from . import fitMobility
|
||||
|
||||
@@ -23,6 +23,7 @@ from functools import lru_cache
|
||||
|
||||
from eos.const import FittingHardpoint
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
|
||||
@@ -256,9 +257,9 @@ def getFighterAbilityMult(fighter, ability, src, distance, tgtSpeed, tgtSigRadiu
|
||||
rangeFactorDistance = None
|
||||
else:
|
||||
rangeFactorDistance = distance + src.getRadius() - fighter.getModifiedItemAttr('radius')
|
||||
rangeFactor = _calcRangeFactor(
|
||||
atkOptimalRange=fighter.getModifiedItemAttr('{}RangeOptimal'.format(attrPrefix)) or fighter.getModifiedItemAttr('{}Range'.format(attrPrefix)),
|
||||
atkFalloffRange=fighter.getModifiedItemAttr('{}RangeFalloff'.format(attrPrefix)),
|
||||
rangeFactor = calculateRangeFactor(
|
||||
srcOptimalRange=fighter.getModifiedItemAttr('{}RangeOptimal'.format(attrPrefix)) or fighter.getModifiedItemAttr('{}Range'.format(attrPrefix)),
|
||||
srcFalloffRange=fighter.getModifiedItemAttr('{}RangeFalloff'.format(attrPrefix)),
|
||||
distance=rangeFactorDistance)
|
||||
drf = fighter.getModifiedItemAttr('{}ReductionFactor'.format(attrPrefix), None)
|
||||
if drf is None:
|
||||
@@ -301,7 +302,7 @@ def _calcTurretChanceToHit(
|
||||
"""Calculate chance to hit for turret-based weapons."""
|
||||
# https://wiki.eveuniversity.org/Turret_mechanics#Hit_Math
|
||||
angularSpeed = _calcAngularSpeed(atkSpeed, atkAngle, atkRadius, distance, tgtSpeed, tgtAngle, tgtRadius)
|
||||
rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
|
||||
rangeFactor = calculateRangeFactor(atkOptimalRange, atkFalloffRange, distance)
|
||||
trackingFactor = _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRadius)
|
||||
cth = rangeFactor * trackingFactor
|
||||
return cth
|
||||
@@ -350,19 +351,7 @@ def _calcAggregatedDrf(reductionFactor, reductionSensitivity):
|
||||
return math.log(reductionFactor) / math.log(reductionSensitivity)
|
||||
|
||||
|
||||
# Generic math
|
||||
def _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance):
|
||||
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
|
||||
if distance is None:
|
||||
return 1
|
||||
if atkFalloffRange > 0:
|
||||
return 0.5 ** ((max(0, distance - atkOptimalRange) / atkFalloffRange) ** 2)
|
||||
elif distance <= atkOptimalRange:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# Misc math
|
||||
def _calcBombFactor(atkEr, tgtSigRadius):
|
||||
if atkEr == 0:
|
||||
return 1
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
import math
|
||||
|
||||
from eos.utils.float import floatUnerr
|
||||
from graphs.calc import calculateRangeFactor
|
||||
from service.const import GraphDpsDroneMode
|
||||
from service.settings import GraphSettings
|
||||
from .application import _calcRangeFactor
|
||||
|
||||
|
||||
def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
|
||||
@@ -39,9 +39,9 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
||||
appliedMultipliers = {}
|
||||
# Modules first, they are applied always the same way
|
||||
for wData in webMods:
|
||||
appliedBoost = wData.boost * _calcRangeFactor(
|
||||
atkOptimalRange=wData.optimal,
|
||||
atkFalloffRange=wData.falloff,
|
||||
appliedBoost = wData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=wData.optimal,
|
||||
srcFalloffRange=wData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
|
||||
@@ -79,9 +79,9 @@ def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
|
||||
rangeFactorDistance = None
|
||||
else:
|
||||
rangeFactorDistance = distance + atkRadius - mwData.radius
|
||||
appliedMwBoost = mwData.boost * _calcRangeFactor(
|
||||
atkOptimalRange=mwData.optimal,
|
||||
atkFalloffRange=mwData.falloff,
|
||||
appliedMwBoost = mwData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=mwData.optimal,
|
||||
srcFalloffRange=mwData.falloff,
|
||||
distance=rangeFactorDistance)
|
||||
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
|
||||
mobileWebs.remove(mwData)
|
||||
@@ -99,9 +99,9 @@ def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
||||
# Modules
|
||||
appliedMultipliers = {}
|
||||
for tpData in tpMods:
|
||||
appliedBoost = tpData.boost * _calcRangeFactor(
|
||||
atkOptimalRange=tpData.optimal,
|
||||
atkFalloffRange=tpData.falloff,
|
||||
appliedBoost = tpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=tpData.optimal,
|
||||
srcFalloffRange=tpData.falloff,
|
||||
distance=distance)
|
||||
if appliedBoost:
|
||||
appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
|
||||
@@ -123,9 +123,9 @@ def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
|
||||
rangeFactorDistance = None
|
||||
else:
|
||||
rangeFactorDistance = distance + atkRadius - mtpData.radius
|
||||
appliedMtpBoost = mtpData.boost * _calcRangeFactor(
|
||||
atkOptimalRange=mtpData.optimal,
|
||||
atkFalloffRange=mtpData.falloff,
|
||||
appliedMtpBoost = mtpData.boost * calculateRangeFactor(
|
||||
srcOptimalRange=mtpData.optimal,
|
||||
srcFalloffRange=mtpData.falloff,
|
||||
distance=rangeFactorDistance)
|
||||
appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
|
||||
tpedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers)
|
||||
|
||||
@@ -91,8 +91,7 @@ class FitDamageStatsGraph(FitGraph):
|
||||
('atkSpeed', '%'): lambda v, src, tgt: v / 100 * src.getMaxVelocity(),
|
||||
('tgtSpeed', '%'): lambda v, src, tgt: v / 100 * tgt.getMaxVelocity(),
|
||||
('tgtSigRad', '%'): lambda v, src, tgt: v / 100 * tgt.getSigRadius()}
|
||||
_limiters = {
|
||||
'time': lambda src, tgt: (0, 2500)}
|
||||
_limiters = {'time': lambda src, tgt: (0, 2500)}
|
||||
_getters = {
|
||||
('distance', 'dps'): Distance2DpsGetter,
|
||||
('distance', 'volley'): Distance2VolleyGetter,
|
||||
|
||||
24
graphs/data/fitEwarStats/__init__.py
Normal file
24
graphs/data/fitEwarStats/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from .graph import FitEwarStatsGraph
|
||||
|
||||
|
||||
FitEwarStatsGraph.register()
|
||||
76
graphs/data/fitEwarStats/getter.py
Normal file
76
graphs/data/fitEwarStats/getter.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from eos.const import FittingModuleState
|
||||
from graphs.calc import calculateMultiplier, calculateRangeFactor
|
||||
from graphs.data.base import SmoothPointGetter
|
||||
|
||||
|
||||
class Distance2WebbingStrengthGetter(SmoothPointGetter):
|
||||
|
||||
_baseResolution = 50
|
||||
_extraDepth = 2
|
||||
|
||||
def _getCommonData(self, miscParams, src, tgt):
|
||||
resist = dict(miscParams)['resist'] or 0
|
||||
webs = []
|
||||
for mod in src.item.modules:
|
||||
if mod.state <= FittingModuleState.ONLINE:
|
||||
continue
|
||||
for webEffectName in ('remoteWebifierFalloff', 'structureModuleEffectStasisWebifier'):
|
||||
if webEffectName in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * (1 - resist),
|
||||
mod.maxRange or 0, mod.falloff or 0, 'default'))
|
||||
if 'doomsdayAOEWeb' in mod.item.effects:
|
||||
webs.append((
|
||||
mod.getModifiedItemAttr('speedFactor') * (1 - resist),
|
||||
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
|
||||
mod.falloff or 0, 'default'))
|
||||
for drone in src.item.drones:
|
||||
if drone.amountActive <= 0:
|
||||
continue
|
||||
if 'remoteWebifierEntity' in drone.item.effects:
|
||||
webs.extend(drone.amountActive * ((
|
||||
drone.getModifiedItemAttr('speedFactor') * (1 - resist),
|
||||
src.item.extraAttributes['droneControlRange'], 0, 'default'),))
|
||||
for fighter in src.item.fighters:
|
||||
if not fighter.active:
|
||||
continue
|
||||
for ability in fighter.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.effect.name == 'fighterAbilityStasisWebifier':
|
||||
webs.append((
|
||||
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amountActive * (1 - resist),
|
||||
math.inf, 0, 'default'))
|
||||
return {'webs': webs}
|
||||
|
||||
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
|
||||
distance = x
|
||||
strMults = {}
|
||||
for strength, optimal, falloff, stackingGroup in commonData['webs']:
|
||||
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
|
||||
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
|
||||
strMult = calculateMultiplier(strMults)
|
||||
strength = (1 - strMult) * 100
|
||||
return strength
|
||||
41
graphs/data/fitEwarStats/graph.py
Normal file
41
graphs/data/fitEwarStats/graph.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# =============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of pyfa.
|
||||
#
|
||||
# pyfa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# pyfa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from graphs.data.base import FitGraph, Input, XDef, YDef
|
||||
from .getter import Distance2WebbingStrengthGetter
|
||||
|
||||
|
||||
class FitEwarStatsGraph(FitGraph):
|
||||
|
||||
# UI stuff
|
||||
internalName = 'ewarStatsGraph'
|
||||
name = 'Electronic Warfare Stats'
|
||||
xDefs = [XDef(handle='distance', unit='km', label='Distance', mainInput=('distance', 'km'))]
|
||||
yDefs = [YDef(handle='webStr', unit='%', label='Webbing strength')]
|
||||
inputs = [
|
||||
Input(handle='distance', unit='km', label='Distance', iconID=1391, defaultValue=None, defaultRange=(0, 100)),
|
||||
Input(handle='resist', unit='%', label='Target resistance', iconID=1393, defaultValue=0, defaultRange=(0, 100))]
|
||||
|
||||
# Calculation stuff
|
||||
_normalizers = {
|
||||
('distance', 'km'): lambda v, src, tgt: None if v is None else v * 1000,
|
||||
('resist', '%'): lambda v, src, tgt: None if v is None else v / 100}
|
||||
_limiters = {'resist': lambda src, tgt: (0, 1)}
|
||||
_getters = {('distance', 'webStr'): Distance2WebbingStrengthGetter}
|
||||
@@ -18,12 +18,11 @@
|
||||
# =============================================================================
|
||||
|
||||
|
||||
import math
|
||||
|
||||
from eos.saveddata.damagePattern import DamagePattern
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from service.const import TargetResistMode
|
||||
from .calc import calculateMultiplier
|
||||
|
||||
|
||||
class BaseWrapper:
|
||||
@@ -64,7 +63,7 @@ class BaseWrapper:
|
||||
elif self.isProfile:
|
||||
maxVelocity = self.item.maxVelocity
|
||||
if extraMultipliers:
|
||||
maxVelocity *= _calculateMultiplier(extraMultipliers)
|
||||
maxVelocity *= calculateMultiplier(extraMultipliers)
|
||||
else:
|
||||
maxVelocity = None
|
||||
return maxVelocity
|
||||
@@ -78,7 +77,7 @@ class BaseWrapper:
|
||||
elif self.isProfile:
|
||||
sigRadius = self.item.signatureRadius
|
||||
if extraMultipliers:
|
||||
sigRadius *= _calculateMultiplier(extraMultipliers)
|
||||
sigRadius *= calculateMultiplier(extraMultipliers)
|
||||
else:
|
||||
sigRadius = None
|
||||
return sigRadius
|
||||
@@ -141,30 +140,6 @@ class TargetWrapper(BaseWrapper):
|
||||
return em, therm, kin, explo
|
||||
|
||||
|
||||
# Just copy-paste penalization chain calculation code (with some modifications,
|
||||
# as multipliers arrive in different form) in here to not make actual attribute
|
||||
# calculations slower than they already are due to extra function calls
|
||||
def _calculateMultiplier(multipliers):
|
||||
val = 1
|
||||
for penalizedMultipliers in multipliers.values():
|
||||
# A quick explanation of how this works:
|
||||
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
|
||||
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
|
||||
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
|
||||
# 2: The most significant bonuses take the smallest penalty,
|
||||
# This means we'll have to sort
|
||||
abssort = lambda _val: -abs(_val - 1)
|
||||
l1.sort(key=abssort)
|
||||
l2.sort(key=abssort)
|
||||
# 3: The first module doesn't get penalized at all
|
||||
# Any module after the first takes penalties according to:
|
||||
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
|
||||
for l in (l1, l2):
|
||||
for i in range(len(l)):
|
||||
bonus = l[i]
|
||||
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
|
||||
return val
|
||||
|
||||
|
||||
def _getShieldResists(ship):
|
||||
em = 1 - ship.getModifiedItemAttr('shieldEmDamageResonance')
|
||||
|
||||
Reference in New Issue
Block a user