Add web strength vs range graph

This commit is contained in:
DarkPhoenix
2019-08-13 08:28:59 +03:00
parent 69bd988174
commit 3bc93899fe
9 changed files with 227 additions and 60 deletions

62
graphs/calc.py Normal file
View 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

View File

@@ -19,6 +19,7 @@
from . import fitDamageStats
from . import fitEwarStats
from . import fitShieldRegen
from . import fitCapRegen
from . import fitMobility

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View 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()

View 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

View 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}

View File

@@ -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')