Extend afflictor info with extra data
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user