Extend afflictor info with extra data

This commit is contained in:
DarkPhoenix
2019-08-21 09:37:06 +03:00
parent 9572a51f28
commit 0e2ae0e0f0
6 changed files with 133 additions and 51 deletions

View File

@@ -101,3 +101,12 @@ class FitSystemSecurity(IntEnum):
LOWSEC = 1
NULLSEC = 2
WSPACE = 3
@unique
class Operator(IntEnum):
PREASSIGN = 0
PREINCREASE = 1
MULTIPLY = 2
POSTINCREASE = 3
FORCE = 4

View File

@@ -20,10 +20,13 @@
import collections
from copy import copy
from math import exp
from eos.const import Operator
# TODO: This needs to be moved out, we shouldn't have *ANY* dependencies back to other modules/methods inside eos.
# This also breaks writing any tests. :(
from eos.db.gamedata.queries import getAttributeInfo
defaultValuesCache = {}
cappingAttrKeyCache = {}
@@ -55,6 +58,11 @@ class ItemAttrShortcut:
return_value = self.itemModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
return return_value or default
def getModifiedItemAttrWithoutAfflictor(self, key, afflictor, default=0):
"""Returns attribute value with passed afflictor modification removed."""
return_value = self.itemModifiedAttributes.getWithoutAfflictor(key, afflictor)
return return_value or default
def getItemBaseAttrValue(self, key, default=0):
return_value = self.itemModifiedAttributes.getOriginal(key)
return return_value or default
@@ -68,7 +76,12 @@ class ChargeAttrShortcut:
def getModifiedChargeAttrWithExtraMods(self, key, extraMultipliers=None, default=0):
"""Returns attribute value with passed modifiers applied to it."""
return_value = self.itemModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
return_value = self.chargeModifiedAttributes.getWithExtraMods(key, extraMultipliers=extraMultipliers)
return return_value or default
def getModifiedChargeAttrWithoutAfflictor(self, key, afflictor, default=0):
"""Returns attribute value with passed modifiers applied to it."""
return_value = self.chargeModifiedAttributes.getWithoutAfflictor(key, afflictor)
return return_value or default
def getChargeBaseAttrValue(self, key, default=0):
@@ -93,6 +106,10 @@ class ModifiedAttributeDict(collections.MutableMapping):
# Final modified values
self.__modified = {}
# Affected by entities
# Format:
# {attr name: {modifying fit: (
# modifying item, operation, stacking group, pre-resist amount,
# post-resist amount, affects result or not)}}
self.__affectedBy = {}
# Overrides (per item)
self.__overrides = {}
@@ -205,6 +222,20 @@ class ModifiedAttributeDict(collections.MutableMapping):
# Passed in default value
return default
def getWithoutAfflictor(self, key, afflictor, default=0):
# Here we do not have support for preAssigns/forceds, as doing them would
# mean that we have to store all of them in a list which increases memory use,
# and we do not actually need those operators atm
preIncreaseAdjustment = 0
multiplierAdjustment = 1
ignoredPenalizedMultipliers = None
postIncreaseAdjustment = 0
# for fit, afflictors in self.getAfflictions(key).items():
# for afflictor1, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
# if afflictor1 is afflictor:
# print(afflictor.item.name, operator, stackingGroup, preResAmount, postResAmount, used)
return self[key]
def __delitem__(self, key):
if key in self.__modified:
del self.__modified[key]
@@ -373,7 +404,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
def iterAfflictions(self):
return self.__affectedBy.__iter__()
def __afflict(self, attributeName, operation, bonus, used=True):
def __afflict(self, attributeName, operator, stackingGroup, preResAmount, postResAmount, used=True):
"""Add modifier to list of things affecting current item"""
# Do nothing if no fit is assigned
fit = self.fit
@@ -399,13 +430,13 @@ class ModifiedAttributeDict(collections.MutableMapping):
modifier = fit.getModifier()
# Add current affliction to list
affs.append((modifier, operation, bonus, used))
affs.append((modifier, operator, stackingGroup, preResAmount, postResAmount, used))
def preAssign(self, attributeName, value, **kwargs):
"""Overwrites original value of the entity with given one, allowing further modification"""
self.__preAssigns[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, "=", value, value != self.getOriginal(attributeName))
self.__afflict(attributeName, Operator.PREASSIGN, None, value, value, value != self.getOriginal(attributeName))
def increase(self, attributeName, increase, position="pre", skill=None, **kwargs):
"""Increase value of given attribute by given number"""
@@ -418,8 +449,10 @@ class ModifiedAttributeDict(collections.MutableMapping):
# Increases applied before multiplications and after them are
# written in separate maps
if position == "pre":
operator = Operator.PREINCREASE
tbl = self.__preIncreases
elif position == "post":
operator = Operator.POSTINCREASE
tbl = self.__postIncreases
else:
raise ValueError("position should be either pre or post")
@@ -427,7 +460,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
tbl[attributeName] = 0
tbl[attributeName] += increase
self.__placehold(attributeName)
self.__afflict(attributeName, "+", increase, increase != 0)
self.__afflict(attributeName, operator, None, increase, increase, increase != 0)
def multiply(self, attributeName, multiplier, stackingPenalties=False, penaltyGroup="default", skill=None, **kwargs):
"""Multiply value of given attribute by given factor"""
@@ -437,6 +470,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
if skill:
multiplier *= self.__handleSkill(skill)
preResMultiplier = multiplier
resisted = False
# Goddammit CCP, make up your mind where you want this information >.< See #1139
if 'effect' in kwargs:
@@ -468,7 +502,9 @@ class ModifiedAttributeDict(collections.MutableMapping):
if resisted:
afflictPenal += "r"
self.__afflict(attributeName, "%s*" % afflictPenal, multiplier, multiplier != 1)
self.__afflict(
attributeName, Operator.MULTIPLY, penaltyGroup if stackingPenalties else None,
preResMultiplier, multiplier, multiplier != 1)
def boost(self, attributeName, boostFactor, skill=None, **kwargs):
"""Boost value by some percentage"""
@@ -482,7 +518,7 @@ class ModifiedAttributeDict(collections.MutableMapping):
"""Force value to attribute and prohibit any changes to it"""
self.__forced[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, "\u2263", value)
self.__afflict(attributeName, Operator.FORCE, None, value, value)
@staticmethod
def getResistance(fit, effect):

View File

@@ -1198,9 +1198,11 @@ class Fit:
def capDelta(self):
return (self.__capRecharge or 0) - (self.__capUsed or 0)
def calculateCapRecharge(self, percent=PEAK_RECHARGE):
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
def calculateCapRecharge(self, percent=PEAK_RECHARGE, capacity=None, rechargeRate=None):
if capacity is None:
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
if rechargeRate is None:
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
return 10 / rechargeRate * sqrt(percent) * (1 - sqrt(percent)) * capacity
def calculateShieldRecharge(self, percent=PEAK_RECHARGE):
@@ -1314,6 +1316,14 @@ class Fit:
self.__savedCapSimData[startingCap] = []
return None
def getCapGainFromMod(self, mod):
"""Return how much cap regen do we gain from having this module"""
currentRegen = self.calculateCapRecharge()
nomodRegen = self.calculateCapRecharge(
capacity=self.ship.getModifiedItemAttrWithoutAfflictor("capacitorCapacity", mod),
rechargeRate=self.ship.getModifiedItemAttrWithoutAfflictor("rechargeRate", mod) / 1000.0)
return currentRegen - nomodRegen
def getRemoteReps(self, spoolOptions=None):
if spoolOptions not in self.__remoteRepMap:
remoteReps = RRTypes(0, 0, 0, 0)
@@ -1417,47 +1427,47 @@ class Fit:
for tankType in localAdjustment:
dict = self.extraAttributes.getAfflictions(tankType)
if self in dict:
for mod, _, amount, used in dict[self]:
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in dict[self]:
if not used:
continue
if mod.projected:
if afflictor.projected:
continue
if mod.item.group.name not in groupAttrMap:
if afflictor.item.group.name not in groupAttrMap:
continue
usesCap = True
try:
if mod.capUse:
capUsed -= mod.capUse
if afflictor.capUse:
capUsed -= afflictor.capUse
else:
usesCap = False
except AttributeError:
usesCap = False
# Normal Repairers
if usesCap and not mod.charge:
cycleTime = mod.rawCycleTime
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
if usesCap and not afflictor.charge:
cycleTime = afflictor.rawCycleTime
amount = afflictor.getModifiedItemAttr(groupAttrMap[afflictor.item.group.name])
localAdjustment[tankType] -= amount / (cycleTime / 1000.0)
repairers.append(mod)
repairers.append(afflictor)
# Ancillary Armor reps etc
elif usesCap and mod.charge:
cycleTime = mod.rawCycleTime
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
if mod.charge.name == "Nanite Repair Paste":
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
elif usesCap and afflictor.charge:
cycleTime = afflictor.rawCycleTime
amount = afflictor.getModifiedItemAttr(groupAttrMap[afflictor.item.group.name])
if afflictor.charge.name == "Nanite Repair Paste":
multiplier = afflictor.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
else:
multiplier = 1
localAdjustment[tankType] -= amount * multiplier / (cycleTime / 1000.0)
repairers.append(mod)
repairers.append(afflictor)
# Ancillary Shield boosters etc
elif not usesCap and mod.item.group.name in ("Ancillary Shield Booster", "Ancillary Remote Shield Booster"):
cycleTime = mod.rawCycleTime
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
if self.factorReload and mod.charge:
reloadtime = mod.reloadTime
elif not usesCap and afflictor.item.group.name in ("Ancillary Shield Booster", "Ancillary Remote Shield Booster"):
cycleTime = afflictor.rawCycleTime
amount = afflictor.getModifiedItemAttr(groupAttrMap[afflictor.item.group.name])
if self.factorReload and afflictor.charge:
reloadtime = afflictor.reloadTime
else:
reloadtime = 0.0
offdutycycle = reloadtime / ((max(mod.numShots, 1) * cycleTime) + reloadtime)
offdutycycle = reloadtime / ((max(afflictor.numShots, 1) * cycleTime) + reloadtime)
localAdjustment[tankType] -= amount * offdutycycle / (cycleTime / 1000.0)
# Sort repairers by efficiency. We want to use the most efficient repairers first
@@ -1469,35 +1479,35 @@ class Fit:
# Most efficient first, as we sorted earlier.
# calculate how much the repper can rep stability & add to total
totalPeakRecharge = self.capRecharge
for mod in repairers:
for afflictor in repairers:
if capUsed > totalPeakRecharge:
break
if self.factorReload and mod.charge:
reloadtime = mod.reloadTime
if self.factorReload and afflictor.charge:
reloadtime = afflictor.reloadTime
else:
reloadtime = 0.0
cycleTime = mod.rawCycleTime
capPerSec = mod.capUse
cycleTime = afflictor.rawCycleTime
capPerSec = afflictor.capUse
if capPerSec is not None and cycleTime is not None:
# Check how much this repper can work
sustainability = min(1, (totalPeakRecharge - capUsed) / capPerSec)
amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
amount = afflictor.getModifiedItemAttr(groupAttrMap[afflictor.item.group.name])
# Add the sustainable amount
if not mod.charge:
localAdjustment[groupStoreMap[mod.item.group.name]] += sustainability * amount / (
if not afflictor.charge:
localAdjustment[groupStoreMap[afflictor.item.group.name]] += sustainability * amount / (
cycleTime / 1000.0)
else:
if mod.charge.name == "Nanite Repair Paste":
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
if afflictor.charge.name == "Nanite Repair Paste":
multiplier = afflictor.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
else:
multiplier = 1
ondutycycle = (max(mod.numShots, 1) * cycleTime) / (
(max(mod.numShots, 1) * cycleTime) + reloadtime)
ondutycycle = (max(afflictor.numShots, 1) * cycleTime) / (
(max(afflictor.numShots, 1) * cycleTime) + reloadtime)
localAdjustment[groupStoreMap[
mod.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
afflictor.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
cycleTime / 1000.0)
capUsed += capPerSec

View File

@@ -60,7 +60,7 @@ class ChangeAffectingSkills(ContextMenuSingle):
continue
for fit, afflictors in cont.getAfflictions(attrName).items():
for afflictor, modifier, amount, used in afflictors:
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
# only add Skills
if not isinstance(afflictor, Skill):
continue

View File

@@ -1,6 +1,7 @@
# noinspection PyPackageRequirements
import wx
from eos.const import Operator
from eos.saveddata.mode import Mode
from eos.saveddata.character import Skill
from eos.saveddata.implant import Implant
@@ -17,6 +18,21 @@ from gui.contextMenu import ContextMenu
from gui.bitmap_loader import BitmapLoader
def formatOperator(operator, stackingGroup, preResAmount, postResAmount):
opMap = {
Operator.PREASSIGN: '=',
Operator.PREINCREASE: '+',
Operator.MULTIPLY: '*',
Operator.POSTINCREASE: '+',
Operator.FORCE: '\u2263'}
prefix = ''
if stackingGroup is not None:
prefix += 's'
if preResAmount != postResAmount:
prefix += 'r'
return '{}{}'.format(prefix, opMap[operator])
class ItemAffectedBy(wx.Panel):
ORDER = [Fit, Ship, Citadel, Mode, Module, Drone, Fighter, Implant, Booster, Skill]
@@ -183,7 +199,7 @@ class ItemAffectedBy(wx.Panel):
continue
for fit, afflictors in attributes.getAfflictions(attrName).items():
for afflictor, modifier, amount, used in afflictors:
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
if not used or afflictor.item is None:
continue
@@ -209,8 +225,10 @@ class ItemAffectedBy(wx.Panel):
else:
item = afflictor.item
items[attrName].append(
(type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False)))
items[attrName].append((
type(afflictor), afflictor, item,
formatOperator(operator, stackingGroup, preResAmount, postResAmount),
postResAmount, getattr(afflictor, "projected", False)))
# Make sure projected fits are on top
rootOrder = list(container.keys())
@@ -316,7 +334,7 @@ class ItemAffectedBy(wx.Panel):
continue
for fit, afflictors in attributes.getAfflictions(attrName).items():
for afflictor, modifier, amount, used in afflictors:
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
if not used or getattr(afflictor, 'item', None) is None:
continue
@@ -343,12 +361,13 @@ class ItemAffectedBy(wx.Panel):
info = items[item.name]
info[1].add(afflictor)
operatorStr = formatOperator(operator, stackingGroup, preResAmount, postResAmount)
# If info[1] > 1, there are two separate modules working.
# Check to make sure we only include the modifier once
# See GH issue 154
if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]:
if len(info[1]) > 1 and (attrName, operatorStr, postResAmount) in info[2]:
continue
info[2].append((attrName, modifier, amount))
info[2].append((attrName, operatorStr, postResAmount))
# Make sure projected fits are on top
rootOrder = list(container.keys())

View File

@@ -444,6 +444,14 @@ class Miscellanea(ViewColumn):
text = "{0}/s".format(formatAmount(rps, 3, 0, 3, forceSign=True))
tooltip = "Structure repaired per second"
return text, tooltip
elif itemGroup in ("Capacitor Recharger", "Capacitor Power Relay", "Capacitor Battery"):
fit = Fit.getInstance().getFit(self.fittingView.getActiveFit())
capGain = fit.getCapGainFromMod(stuff)
if not capGain:
return "", None
text = formatAmount(capGain, 3, 0, 3, forceSign=True)
tooltip = "Peak capacitor regeneration gain"
return text, tooltip
elif itemGroup == "Gang Coordinator":
command = stuff.getModifiedItemAttr("commandBonus") or stuff.getModifiedItemAttr("commandBonusHidden")
if not command: