From 1c120f2fd6ebbeb527f24ca69b8963ecf6c8ca9d Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Mon, 8 Jul 2019 18:48:25 +0300 Subject: [PATCH] Apply all webs including drones to target --- gui/builtinContextMenus/graphDmgDroneMode.py | 4 +- gui/builtinGraphs/fitDamageStats/calc.py | 37 ++++++++++++++++++- gui/builtinGraphs/fitDamageStats/graph.py | 3 ++ .../fitDamageStats/projectedCache.py | 22 +++++++---- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/gui/builtinContextMenus/graphDmgDroneMode.py b/gui/builtinContextMenus/graphDmgDroneMode.py index 93bf687db..53827c311 100644 --- a/gui/builtinContextMenus/graphDmgDroneMode.py +++ b/gui/builtinContextMenus/graphDmgDroneMode.py @@ -38,8 +38,8 @@ class GraphDmgDroneModeMenu(ContextMenuUnconditional): self.idOptionMap = {} optionMap = OrderedDict([ (GraphDpsDroneMode.auto, 'Auto'), - (GraphDpsDroneMode.followAttacker, 'Stick to Attacker'), - (GraphDpsDroneMode.followTarget, 'Stick to Target')]) + (GraphDpsDroneMode.followTarget, 'Stick to Target'), + (GraphDpsDroneMode.followAttacker, 'Stick to Attacker')]) for option, label in optionMap.items(): menuId = ContextMenuUnconditional.nextID() item = wx.MenuItem(m, menuId, label, kind=wx.ITEM_CHECK) diff --git a/gui/builtinGraphs/fitDamageStats/calc.py b/gui/builtinGraphs/fitDamageStats/calc.py index 812e55f9d..7530370a3 100644 --- a/gui/builtinGraphs/fitDamageStats/calc.py +++ b/gui/builtinGraphs/fitDamageStats/calc.py @@ -171,7 +171,7 @@ def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadiu return mult -def applyWebs(tgt, currentUnwebbedSpeed, webMods, distance): +def applyWebs(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance): if tgt.ship.getModifiedItemAttr('disallowOffensiveModifiers'): return currentUnwebbedSpeed unwebbedSpeed = tgt.ship.getModifiedItemAttr('maxVelocity') @@ -181,11 +181,46 @@ def applyWebs(tgt, currentUnwebbedSpeed, webMods, distance): currentWebbedSpeed = 0 else: appliedMultipliers = {} + # Modules first, they are applied always the same way for boost, optimal, falloff, stackingChain, resistanceAttrID in webMods: appliedBoost = boost * _calcRangeFactor(atkOptimalRange=optimal, atkFalloffRange=falloff, distance=distance) if appliedBoost: appliedMultipliers.setdefault(stackingChain, []).append((1 + appliedBoost / 100, resistanceAttrID)) webbedSpeed = tgt.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=appliedMultipliers) + # Drones and fighters + mobileWebs = [] + mobileWebs.extend(webFighters) + # Drones have range limit + if distance <= fit.extraAttributes['droneControlRange']: + mobileWebs.extend(webDrones) + atkRadius = fit.ship.getModifiedItemAttr('radius') + # As mobile webs either follow the target or stick to the attacking ship, + # if target is within mobile web optimal - it can be applied unconditionally + longEnoughMws = [mw for mw in mobileWebs if distance <= mw.optimal - atkRadius + mw.radius] + if longEnoughMws: + for mwData in longEnoughMws: + appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID)) + mobileWebs.remove(mwData) + webbedSpeed = tgt.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=appliedMultipliers) + # Apply remaining webs, from fastest to slowest + droneOpt = GraphSettings.getInstance().get('mobileDroneMode') + while mobileWebs: + # Process in batches unified by speed to save up resources + fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed + fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed] + for mwData in fastestMws: + # Faster than target or set to follow it - apply full slowdown + if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= webbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget: + appliedMwBoost = mwData.boost + # Otherwise project from the center of the ship + else: + appliedMwBoost = mwData.boost * _calcRangeFactor( + atkOptimalRange=mwData.optimal, + atkFalloffRange=mwData.falloff, + distance=distance + fit.ship.getModifiedItemAttr('radius') - mwData.radius) + appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID)) + mobileWebs.remove(mwData) + webbedSpeed = tgt.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=appliedMultipliers) currentWebbedSpeed = webbedSpeed * speedRatio return currentWebbedSpeed diff --git a/gui/builtinGraphs/fitDamageStats/graph.py b/gui/builtinGraphs/fitDamageStats/graph.py index e1fa0bda9..731ffa926 100644 --- a/gui/builtinGraphs/fitDamageStats/graph.py +++ b/gui/builtinGraphs/fitDamageStats/graph.py @@ -182,9 +182,12 @@ class FitDamageStatsGraph(FitGraph): webDrones, tpDrones = self._projectedCache.getProjDroneData(fit) webFighters, tpFighters = self._projectedCache.getProjFighterData(fit) tgtSpeed = applyWebs( + fit=fit, tgt=tgt, currentUnwebbedSpeed=miscInputMap['tgtSpeed'], webMods=webMods, + webDrones=webDrones, + webFighters=webFighters, distance=distance) tgtSigRadius = tgt.ship.getModifiedItemAttr('signatureRadius') * applyTps( tgt=tgt, diff --git a/gui/builtinGraphs/fitDamageStats/projectedCache.py b/gui/builtinGraphs/fitDamageStats/projectedCache.py index fc9bc7bce..43d1f242d 100644 --- a/gui/builtinGraphs/fitDamageStats/projectedCache.py +++ b/gui/builtinGraphs/fitDamageStats/projectedCache.py @@ -18,11 +18,17 @@ # ============================================================================= +from collections import namedtuple + from gui.builtinGraphs.base import FitDataCache from eos.const import FittingModuleState from eos.modifiedAttributeDict import getResistanceAttrID +ModProjData = namedtuple('ModProjData', ('boost', 'optimal', 'falloff', 'stackingGroup', 'resAttrID')) +MobileProjData = namedtuple('MobileProjData', ('boost', 'optimal', 'falloff', 'stackingGroup', 'resAttrID', 'speed', 'radius')) + + class ProjectedDataCache(FitDataCache): def getProjModData(self, fit): @@ -38,14 +44,14 @@ class ProjectedDataCache(FitDataCache): continue for webEffectName in ('remoteWebifierFalloff', 'structureModuleEffectStasisWebifier'): if webEffectName in mod.item.effects: - webMods.append(( + webMods.append(ModProjData( mod.getModifiedItemAttr('speedFactor'), mod.maxRange or 0, mod.falloff or 0, 'default', getResistanceAttrID(modifyingItem=mod, effect=mod.item.effects[webEffectName]))) if 'doomsdayAOEWeb' in mod.item.effects: - webMods.append(( + webMods.append(ModProjData( mod.getModifiedItemAttr('speedFactor'), max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - fit.ship.getModifiedItemAttr('radius')), mod.falloff or 0, @@ -53,14 +59,14 @@ class ProjectedDataCache(FitDataCache): getResistanceAttrID(modifyingItem=mod, effect=mod.item.effects['doomsdayAOEWeb']))) for tpEffectName in ('remoteTargetPaintFalloff', 'structureModuleEffectTargetPainter'): if tpEffectName in mod.item.effects: - tpMods.append(( + tpMods.append(ModProjData( mod.getModifiedItemAttr('signatureRadiusBonus'), mod.maxRange or 0, mod.falloff or 0, 'default', getResistanceAttrID(modifyingItem=mod, effect=mod.item.effects[tpEffectName]))) if 'doomsdayAOEPaint' in mod.item.effects: - tpMods.append(( + tpMods.append(ModProjData( mod.getModifiedItemAttr('signatureRadiusBonus'), max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - fit.ship.getModifiedItemAttr('radius')), mod.falloff or 0, @@ -80,7 +86,7 @@ class ProjectedDataCache(FitDataCache): if drone.amountActive <= 0: continue if 'remoteWebifierEntity' in drone.item.effects: - webDrones.extend(drone.amountActive * (( + webDrones.extend(drone.amountActive * (MobileProjData( drone.getModifiedItemAttr('speedFactor'), drone.maxRange or 0, drone.falloff or 0, @@ -89,7 +95,7 @@ class ProjectedDataCache(FitDataCache): drone.getModifiedItemAttr('maxVelocity'), drone.getModifiedItemAttr('radius')),)) if 'remoteTargetPaintEntity' in drone.item.effects: - tpDrones.extend(drone.amountActive * (( + tpDrones.extend(drone.amountActive * (MobileProjData( drone.getModifiedItemAttr('signatureRadiusBonus'), drone.maxRange or 0, drone.falloff or 0, @@ -103,7 +109,7 @@ class ProjectedDataCache(FitDataCache): try: projectedData = self._data[fit.ID]['fighters'] except KeyError: - # Format of items for both: (boost strength, optimal, falloff, stacking group, resistance attr ID, drone speed, drone radius) + # Format of items for both: (boost strength, optimal, falloff, stacking group, resistance attr ID, fighter speed, fighter radius) webFighters = [] tpFighters = [] projectedData = self._data.setdefault(fit.ID, {})['fighters'] = (webFighters, tpFighters) @@ -114,7 +120,7 @@ class ProjectedDataCache(FitDataCache): if not ability.active: continue if ability.effect.name == 'fighterAbilityStasisWebifier': - webFighters.extend(( + webFighters.append(MobileProjData( fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amountActive, fighter.getModifiedItemAttr('fighterAbilityStasisWebifierOptimalRange'), fighter.getModifiedItemAttr('fighterAbilityStasisWebifierFalloffRange'),