Merge remote-tracking branch 'origin/master' into attrGroup
# Conflicts: # eve.db
This commit is contained in:
@@ -40,7 +40,9 @@ items_table = Table("invtypes", gamedata_meta,
|
||||
Column("marketGroupID", Integer, ForeignKey("invmarketgroups.marketGroupID")),
|
||||
Column("iconID", Integer),
|
||||
Column("graphicID", Integer),
|
||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True))
|
||||
Column("groupID", Integer, ForeignKey("invgroups.groupID"), index=True),
|
||||
Column("replaceSame", String),
|
||||
Column("replaceBetter", String))
|
||||
|
||||
from .metaGroup import metatypes_table # noqa
|
||||
from .traits import traits_table # noqa
|
||||
|
||||
18
eos/db/migrations/upgrade29.py
Normal file
18
eos/db/migrations/upgrade29.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Migration 29
|
||||
|
||||
- adds spoolType and spoolAmount to modules table
|
||||
"""
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT spoolType FROM modules LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN spoolType INT;")
|
||||
|
||||
try:
|
||||
saveddata_engine.execute("SELECT spoolAmount FROM modules LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
saveddata_engine.execute("ALTER TABLE modules ADD COLUMN spoolAmount FLOAT;")
|
||||
17
eos/db/migrations/upgrade30.py
Normal file
17
eos/db/migrations/upgrade30.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Migration 30
|
||||
|
||||
- changes to prices table
|
||||
"""
|
||||
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
|
||||
def upgrade(saveddata_engine):
|
||||
try:
|
||||
saveddata_engine.execute("SELECT status FROM prices LIMIT 1")
|
||||
except sqlalchemy.exc.DatabaseError:
|
||||
# Just drop table, table will be re-created by sqlalchemy and
|
||||
# data will be re-fetched
|
||||
saveddata_engine.execute("DROP TABLE prices;")
|
||||
@@ -17,7 +17,7 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
from sqlalchemy import Table, Column, Integer, ForeignKey, CheckConstraint, Boolean, DateTime
|
||||
from sqlalchemy import Table, Column, Integer, Float, ForeignKey, CheckConstraint, Boolean, DateTime
|
||||
from sqlalchemy.orm.collections import attribute_mapped_collection
|
||||
from sqlalchemy.orm import relation, mapper
|
||||
import datetime
|
||||
@@ -40,6 +40,8 @@ modules_table = Table("modules", saveddata_meta,
|
||||
Column("position", Integer),
|
||||
Column("created", DateTime, nullable=True, default=datetime.datetime.now),
|
||||
Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now),
|
||||
Column("spoolType", Integer, nullable=True),
|
||||
Column("spoolAmount", Float, nullable=True),
|
||||
CheckConstraint('("dummySlot" = NULL OR "itemID" = NULL) AND "dummySlot" != "itemID"'))
|
||||
|
||||
mapper(Module, modules_table,
|
||||
|
||||
@@ -17,17 +17,20 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
from sqlalchemy import Table, Column, Float, Integer
|
||||
from sqlalchemy.orm import mapper
|
||||
|
||||
from eos.db import saveddata_meta
|
||||
from eos.saveddata.price import Price
|
||||
|
||||
|
||||
prices_table = Table("prices", saveddata_meta,
|
||||
Column("typeID", Integer, primary_key=True),
|
||||
Column("price", Float, default=0.0),
|
||||
Column("time", Integer, nullable=False),
|
||||
Column("failed", Integer))
|
||||
Column("status", Integer, nullable=False))
|
||||
|
||||
|
||||
mapper(Price, prices_table, properties={
|
||||
"_Price__price": prices_table.c.price,
|
||||
|
||||
@@ -542,8 +542,17 @@ def commit():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.commit()
|
||||
saveddata_session.flush()
|
||||
except Exception as ex:
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||
|
||||
|
||||
def flush():
|
||||
with sd_lock:
|
||||
try:
|
||||
saveddata_session.flush()
|
||||
except Exception:
|
||||
saveddata_session.rollback()
|
||||
exc_info = sys.exc_info()
|
||||
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
|
||||
|
||||
@@ -141,6 +141,23 @@ class HandledModuleList(HandledList):
|
||||
self.remove(mod)
|
||||
return
|
||||
|
||||
def replaceRackPosition(self, rackPosition, mod):
|
||||
listPositions = []
|
||||
for currMod in self:
|
||||
if currMod.slot == mod.slot:
|
||||
listPositions.append(currMod.position)
|
||||
listPositions.sort()
|
||||
try:
|
||||
modListPosition = listPositions[rackPosition]
|
||||
except IndexError:
|
||||
self.appendIgnoreEmpty(mod)
|
||||
else:
|
||||
self.toDummy(modListPosition)
|
||||
if not mod.isEmpty:
|
||||
self.toModule(modListPosition, mod)
|
||||
if mod.isInvalid:
|
||||
self.toDummy(modListPosition)
|
||||
|
||||
def insert(self, index, mod):
|
||||
mod.position = index
|
||||
i = index
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceCapNeed
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (493 of 947)
|
||||
# Items from category: Charge (493 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoInfluenceRange
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (587 of 947)
|
||||
# Items from category: Charge (587 of 949)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# ammoSpeedMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Charges from group: Festival Charges (23 of 23)
|
||||
# Charges from group: Festival Charges (26 of 26)
|
||||
# Charges from group: Interdiction Probe (2 of 2)
|
||||
# Charges from group: Structure Festival Charges (3 of 3)
|
||||
# Special Edition Assetss from group: Festival Charges Expired (2 of 2)
|
||||
# Items from market group: Special Edition Assets > Special Edition Festival Assets (30 of 33)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ammoTrackingMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Items from category: Charge (182 of 947)
|
||||
# Items from category: Charge (182 of 949)
|
||||
# Charges from group: Projectile Ammo (128 of 128)
|
||||
type = "passive"
|
||||
|
||||
|
||||
@@ -9,4 +9,7 @@ type = "active"
|
||||
def handler(fit, module, context):
|
||||
amount = module.getModifiedItemAttr("armorDamageAmount")
|
||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||
fit.extraAttributes.increase("armorRepair", amount / speed)
|
||||
rps = amount / speed
|
||||
fit.extraAttributes.increase("armorRepair", rps)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# Implants named like: Eifyr and Co. 'Alchemist' Neurotoxin Control NC (2 of 2)
|
||||
# Implants named like: grade Edge (10 of 12)
|
||||
# Skill: Neurotoxin Control
|
||||
runTime = 'early'
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# boosterShieldCapacityPenalty
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Booster (12 of 69)
|
||||
# Implants from group: Booster (12 of 70)
|
||||
type = "boosterSideEffect"
|
||||
|
||||
# User-friendly name for the side effect
|
||||
|
||||
@@ -6,5 +6,5 @@ type = "passive"
|
||||
|
||||
|
||||
def handler(fit, ship, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
|
||||
"duration", ship.getModifiedItemAttr("durationBonus"))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cynosuralGeneration
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Cynosural Field (2 of 2)
|
||||
# Modules from group: Cynosural Field Generator (2 of 2)
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@ type = "passive"
|
||||
|
||||
def handler(fit, container, context):
|
||||
level = container.level if "skill" in context else 1
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field",
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Cynosural Field Generator",
|
||||
"consumptionQuantity",
|
||||
container.getModifiedItemAttr("consumptionQuantityBonusPercentage") * level)
|
||||
|
||||
@@ -11,6 +11,5 @@ def handler(fit, module, context):
|
||||
bonus = "%s%sDamageResonance" % (attrPrefix, damageType)
|
||||
bonus = "%s%s" % (bonus[0].lower(), bonus[1:])
|
||||
booster = "%s%sDamageResonance" % (layer, damageType)
|
||||
penalize = False if layer == 'hull' else True
|
||||
fit.ship.multiplyItemAttr(bonus, module.getModifiedItemAttr(booster),
|
||||
stackingPenalties=penalize, penaltyGroup="preMul")
|
||||
stackingPenalties=True, penaltyGroup="preMul")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# doHacking
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Data Miners (9 of 9)
|
||||
# Modules from group: Data Miners (10 of 10)
|
||||
type = "active"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneArmorDamageBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneHullRepairBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# droneShieldBonusBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (5 of 6)
|
||||
# Ships from group: Logistics (6 of 7)
|
||||
# Ship: Exequror
|
||||
# Ship: Scythe
|
||||
type = "passive"
|
||||
|
||||
@@ -8,4 +8,6 @@ runtime = "late"
|
||||
|
||||
def handler(fit, src, context):
|
||||
for dmgType in ('em', 'thermal', 'kinetic', 'explosive'):
|
||||
fit.ship.forceItemAttr('{}DamageResonance'.format(dmgType), src.getModifiedItemAttr("hull{}DamageResonance".format(dmgType.title())))
|
||||
fit.ship.multiplyItemAttr('{}DamageResonance'.format(dmgType),
|
||||
src.getModifiedItemAttr("hull{}DamageResonance".format(dmgType.title())),
|
||||
stackingPenalties=True, penaltyGroup="postMul")
|
||||
|
||||
@@ -14,4 +14,7 @@ def handler(fit, module, context):
|
||||
|
||||
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||
fit.extraAttributes.increase("armorRepair", amount / speed)
|
||||
rps = amount / speed
|
||||
fit.extraAttributes.increase("armorRepair", rps)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Ice Harvester Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
10
eos/effects/implantwarpscramblerangebonus.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# implantWarpScrambleRangeBonus
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Warp Scrambler", "maxRange",
|
||||
src.getModifiedItemAttr("warpScrambleRangeBonus"), stackingPenalties=False)
|
||||
@@ -6,4 +6,4 @@ type = "passive"
|
||||
|
||||
|
||||
def handler(fit, module, context):
|
||||
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"))
|
||||
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"), stackingPenalties=True)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
10
eos/effects/miningdurationmultiplieronline.py
Normal file
10
eos/effects/miningdurationmultiplieronline.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# miningDurationMultiplierOnline
|
||||
#
|
||||
# Used by:
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, module, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mining Laser",
|
||||
"duration", module.getModifiedItemAttr("miningDurationMultiplier"))
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Variations of module: Mining Laser Upgrade I (5 of 5)
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Missile Guidance Enhancer (3 of 3)
|
||||
# Module: ML-EKP 'Polybolos' Ballistic Control System
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileDMGBonus
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ballistic Control system (21 of 21)
|
||||
# Modules from group: Ballistic Control system (22 of 22)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# missileLauncherSpeedMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ballistic Control system (21 of 21)
|
||||
# Modules from group: Ballistic Control system (22 of 22)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -9,4 +9,7 @@ def handler(fit, container, context):
|
||||
if "projected" in context:
|
||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||
duration = container.getModifiedItemAttr("duration") / 1000.0
|
||||
fit.extraAttributes.increase("armorRepair", bonus / duration)
|
||||
rps = bonus / duration
|
||||
fit.extraAttributes.increase("armorRepair", rps)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Missile Launcher Torpedo (22 of 22)
|
||||
# Items from market group: Ship Equipment > Turrets & Bays (429 of 881)
|
||||
# Items from market group: Ship Equipment > Turrets & Bays (429 of 883)
|
||||
# Module: Interdiction Sphere Launcher I
|
||||
type = "overheat"
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# overloadSelfDurationBonus
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||
# Modules from group: Capacitor Booster (59 of 59)
|
||||
# Modules from group: Energy Neutralizer (54 of 54)
|
||||
# Modules from group: Energy Nosferatu (54 of 54)
|
||||
# Modules from group: Hull Repair Unit (25 of 25)
|
||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
||||
# Modules from group: Remote Shield Booster (38 of 38)
|
||||
# Modules from group: Smart Bomb (118 of 118)
|
||||
# Modules from group: Warp Disrupt Field Generator (7 of 7)
|
||||
# Modules named like: Ancillary Remote (8 of 8)
|
||||
# Modules named like: Remote Repairer (56 of 56)
|
||||
# Module: Reactive Armor Hardener
|
||||
# Module: Target Spectrum Breaker
|
||||
type = "overheat"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# remoteCapacitorTransmitterPowerNeedBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
|
||||
# Implants named like: Inquest 'Hedone' Entanglement Optimizer WS (3 of 3)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# shieldTransportCpuNeedBonusEffect
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRemoteRepairRangeRole3
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Rodiva
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "maxRange", src.getModifiedItemAttr("shipBonusRole3"))
|
||||
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRemoteRepAmounteliteBonusLogisitics2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Zarmazd
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "armorDamageAmount", src.getModifiedItemAttr("eliteBonusLogistics2"), skill="Logistics Cruisers")
|
||||
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRemoteRepCapNeedeliteBonusLogisitics1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Zarmazd
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "capacitorNeed", src.getModifiedItemAttr("eliteBonusLogistics1"), skill="Logistics Cruisers")
|
||||
7
eos/effects/shipbonusmutadaptiveremotereprangepc1.py
Normal file
7
eos/effects/shipbonusmutadaptiveremotereprangepc1.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRemoteRepRangePC1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Zarmazd
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "maxRange", src.getModifiedItemAttr("shipBonusPC1"), skill="Precursor Cruiser")
|
||||
7
eos/effects/shipbonusmutadaptiverepamountpc1.py
Normal file
7
eos/effects/shipbonusmutadaptiverepamountpc1.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRepAmountPC1
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Rodiva
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "armorDamageAmount", src.getModifiedItemAttr("shipBonusPC1"), skill="Precursor Cruiser")
|
||||
7
eos/effects/shipbonusmutadaptiverepcapneedpc2.py
Normal file
7
eos/effects/shipbonusmutadaptiverepcapneedpc2.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# shipBonusMutadaptiveRepCapNeedPC2
|
||||
#
|
||||
# Used by:
|
||||
# Ship: Rodiva
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Mutadaptive Remote Armor Repairer", "capacitorNeed", src.getModifiedItemAttr("shipBonusPC2"), skill="Precursor Cruiser")
|
||||
7
eos/effects/shipbonusnosneutcapneedrolebonus2.py
Normal file
7
eos/effects/shipbonusnosneutcapneedrolebonus2.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# shipBonusNosNeutCapNeedRoleBonus2
|
||||
#
|
||||
# Used by:
|
||||
# Variations of ship: Rodiva (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Capacitor Emission Systems"), "capacitorNeed", src.getModifiedItemAttr("shipBonusRole2"))
|
||||
@@ -0,0 +1,7 @@
|
||||
# shipBonusRemoteCapacitorTransferRangeRole1
|
||||
#
|
||||
# Used by:
|
||||
# Variations of ship: Rodiva (2 of 2)
|
||||
type = "passive"
|
||||
def handler(fit, src, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Remote Capacitor Transmitter", "maxRange", src.getModifiedItemAttr("shipBonusRole1"))
|
||||
@@ -1,7 +1,7 @@
|
||||
# shipBonusRole5RemoteArmorRepairPowergridBonus
|
||||
#
|
||||
# Used by:
|
||||
# Ships from group: Logistics (3 of 6)
|
||||
# Ships from group: Logistics (3 of 7)
|
||||
type = "passive"
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# shipBonusSmartbombCapNeedRoleBonus2
|
||||
#
|
||||
# Used by:
|
||||
# Variations of ship: Rodiva (2 of 2)
|
||||
# Ship: Damavik
|
||||
# Ship: Drekavac
|
||||
# Ship: Hydra
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ancillary Remote Armor Repairer (4 of 4)
|
||||
runTime = "late"
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, module, context, **kwargs):
|
||||
@@ -17,4 +18,7 @@ def handler(fit, module, context, **kwargs):
|
||||
|
||||
amount = module.getModifiedItemAttr("armorDamageAmount") * multiplier
|
||||
speed = module.getModifiedItemAttr("duration") / 1000.0
|
||||
fit.extraAttributes.increase("armorRepair", amount / speed, **kwargs)
|
||||
rps = amount / speed
|
||||
fit.extraAttributes.increase("armorRepair", rps)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Ancillary Remote Shield Booster (4 of 4)
|
||||
runTime = "late"
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, module, context, **kwargs):
|
||||
|
||||
28
eos/effects/shipmoduleremotearmormutadaptiverepairer.py
Normal file
28
eos/effects/shipmoduleremotearmormutadaptiverepairer.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# ShipModuleRemoteArmorMutadaptiveRepairer
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Mutadaptive Remote Armor Repairer (5 of 5)
|
||||
|
||||
|
||||
from eos.utils.spoolSupport import SpoolType, SpoolOptions, calculateSpoolup, resolveSpoolOptions
|
||||
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, container, context, **kwargs):
|
||||
if "projected" in context:
|
||||
repAmountBase = container.getModifiedItemAttr("armorDamageAmount")
|
||||
cycleTime = container.getModifiedItemAttr("duration") / 1000.0
|
||||
repSpoolMax = container.getModifiedItemAttr("repairMultiplierBonusMax")
|
||||
repSpoolPerCycle = container.getModifiedItemAttr("repairMultiplierBonusPerCycle")
|
||||
# TODO: fetch spoolup option
|
||||
defaultSpoolValue = 1
|
||||
spoolType, spoolAmount = resolveSpoolOptions(SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False), container)
|
||||
rps = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, spoolType, spoolAmount)[0]) / cycleTime
|
||||
rpsPreSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 0)[0]) / cycleTime
|
||||
rpsFullSpool = repAmountBase * (1 + calculateSpoolup(repSpoolMax, repSpoolPerCycle, cycleTime, SpoolType.SCALE, 1)[0]) / cycleTime
|
||||
fit.extraAttributes.increase("armorRepair", rps, **kwargs)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rpsPreSpool, **kwargs)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rpsFullSpool, **kwargs)
|
||||
@@ -2,11 +2,16 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Remote Armor Repairer (39 of 39)
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, container, context, **kwargs):
|
||||
if "projected" in context:
|
||||
bonus = container.getModifiedItemAttr("armorDamageAmount")
|
||||
duration = container.getModifiedItemAttr("duration") / 1000.0
|
||||
fit.extraAttributes.increase("armorRepair", bonus / duration, **kwargs)
|
||||
rps = bonus / duration
|
||||
fit.extraAttributes.increase("armorRepair", rps)
|
||||
fit.extraAttributes.increase("armorRepairPreSpool", rps)
|
||||
fit.extraAttributes.increase("armorRepairFullSpool", rps)
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Remote Capacitor Transmitter (41 of 41)
|
||||
|
||||
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict
|
||||
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
def handler(fit, src, context, **kwargs):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#
|
||||
# Used by:
|
||||
# Modules from group: Remote Hull Repairer (8 of 8)
|
||||
|
||||
type = "projected", "active"
|
||||
runTime = "late"
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ type = "passive"
|
||||
|
||||
def handler(fit, skill, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Missile Launcher Bomb",
|
||||
"moduleReactivationDelay", skill.getModifiedItemAttr("rofBonus") * skill.level)
|
||||
"moduleReactivationDelay", skill.getModifiedItemAttr("reactivationDelayBonus") * skill.level)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# skillBonusDroneDurability
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (4 of 4)
|
||||
# Skill: Drone Durability
|
||||
type = "passive"
|
||||
|
||||
|
||||
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
14
eos/effects/skillbonusdronedurabilitynotfighters.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# skillBonusDroneDurabilityNotFighters
|
||||
#
|
||||
# Used by:
|
||||
# Implants from group: Cyber Drones (4 of 4)
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, src, context):
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "hp",
|
||||
src.getModifiedItemAttr("hullHpBonus"))
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "armorHP",
|
||||
src.getModifiedItemAttr("armorHpBonus"))
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "shieldCapacity",
|
||||
src.getModifiedItemAttr("shieldCapacityBonus"))
|
||||
@@ -1,8 +1,6 @@
|
||||
# skillBonusDroneInterfacing
|
||||
#
|
||||
# Used by:
|
||||
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
|
||||
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
|
||||
# Skill: Drone Interfacing
|
||||
type = "passive"
|
||||
|
||||
|
||||
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
11
eos/effects/skillbonusdroneinterfacingnotfighters.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# skillBonusDroneInterfacingNotFighters
|
||||
#
|
||||
# Used by:
|
||||
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
|
||||
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, src, context):
|
||||
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "damageMultiplier",
|
||||
src.getModifiedItemAttr("damageMultiplierBonus"))
|
||||
10
eos/effects/stripminerdurationmultiplier.py
Normal file
10
eos/effects/stripminerdurationmultiplier.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# stripMinerDurationMultiplier
|
||||
#
|
||||
# Used by:
|
||||
# Module: Frostline 'Omnivore' Harvester Upgrade
|
||||
type = "passive"
|
||||
|
||||
|
||||
def handler(fit, module, context):
|
||||
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Strip Miner",
|
||||
"duration", module.getModifiedItemAttr("miningDurationMultiplier"))
|
||||
@@ -239,7 +239,7 @@ class Item(EqBase):
|
||||
self.__offensive = None
|
||||
self.__assistive = None
|
||||
self.__overrides = None
|
||||
self.__price = None
|
||||
self.__priceObj = None
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
@@ -446,34 +446,33 @@ class Item(EqBase):
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
|
||||
# todo: use `from sqlalchemy import inspect` instead (mac-deprecated doesn't have inspect(), was imp[lemented in 0.8)
|
||||
if self.__price is not None and getattr(self.__price, '_sa_instance_state', None) and self.__price._sa_instance_state.deleted:
|
||||
if self.__priceObj is not None and getattr(self.__priceObj, '_sa_instance_state', None) and self.__priceObj._sa_instance_state.deleted:
|
||||
pyfalog.debug("Price data for {} was deleted (probably from a cache reset), resetting object".format(self.ID))
|
||||
self.__price = None
|
||||
self.__priceObj = None
|
||||
|
||||
if self.__price is None:
|
||||
if self.__priceObj is None:
|
||||
db_price = eos.db.getPrice(self.ID)
|
||||
# do not yet have a price in the database for this item, create one
|
||||
if db_price is None:
|
||||
pyfalog.debug("Creating a price for {}".format(self.ID))
|
||||
self.__price = types_Price(self.ID)
|
||||
eos.db.add(self.__price)
|
||||
eos.db.commit()
|
||||
self.__priceObj = types_Price(self.ID)
|
||||
eos.db.add(self.__priceObj)
|
||||
eos.db.flush()
|
||||
else:
|
||||
self.__price = db_price
|
||||
self.__priceObj = db_price
|
||||
|
||||
return self.__price
|
||||
return self.__priceObj
|
||||
|
||||
@property
|
||||
def isAbyssal(self):
|
||||
if Item.ABYSSAL_TYPES is None:
|
||||
Item.getAbyssalYypes()
|
||||
Item.getAbyssalTypes()
|
||||
|
||||
return self.ID in Item.ABYSSAL_TYPES
|
||||
|
||||
@classmethod
|
||||
def getAbyssalYypes(cls):
|
||||
def getAbyssalTypes(cls):
|
||||
cls.ABYSSAL_TYPES = eos.db.getAbyssalTypes()
|
||||
|
||||
@property
|
||||
|
||||
@@ -75,7 +75,7 @@ class FitDpsGraph(Graph):
|
||||
pyfalog.critical(e)
|
||||
|
||||
for mod in fit.modules:
|
||||
dps, _ = mod.damageStats(fit.targetResists)
|
||||
dps = mod.getDps(targetResists=fit.targetResists).total
|
||||
if mod.hardpoint == Hardpoint.TURRET:
|
||||
if mod.state >= State.ACTIVE:
|
||||
total += dps * self.calculateTurretMultiplier(mod, data)
|
||||
@@ -88,7 +88,7 @@ class FitDpsGraph(Graph):
|
||||
for drone in fit.drones:
|
||||
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
|
||||
drone, data)
|
||||
dps, _ = drone.damageStats(fit.targetResists)
|
||||
dps = drone.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
# this is janky as fuck
|
||||
@@ -98,7 +98,7 @@ class FitDpsGraph(Graph):
|
||||
for ability in fighter.abilities:
|
||||
if ability.dealsDamage and ability.active:
|
||||
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
||||
dps, _ = ability.damageStats(fit.targetResists)
|
||||
dps = ability.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
return total
|
||||
|
||||
@@ -215,6 +215,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
if force is not None:
|
||||
if cappingValue is not None:
|
||||
force = min(force, cappingValue)
|
||||
if key in (50, 30, 48, 11):
|
||||
force = round(force, 2)
|
||||
return force
|
||||
# Grab our values if they're there, otherwise we'll take default values
|
||||
preIncrease = self.__preIncreases.get(key, 0)
|
||||
@@ -268,7 +270,8 @@ class ModifiedAttributeDict(collections.MutableMapping):
|
||||
# Cap value if we have cap defined
|
||||
if cappingValue is not None:
|
||||
val = min(val, cappingValue)
|
||||
|
||||
if key in (50, 30, 48, 11):
|
||||
val = round(val, 2)
|
||||
return val
|
||||
|
||||
def __handleSkill(self, skillName):
|
||||
|
||||
@@ -42,13 +42,18 @@ class DamagePattern(object):
|
||||
return ehp
|
||||
|
||||
def calculateEffectiveTank(self, fit, tankInfo):
|
||||
ehps = {}
|
||||
passiveShield = fit.calculateShieldRecharge()
|
||||
ehps["passiveShield"] = self.effectivify(fit, passiveShield, "shield")
|
||||
for type in ("shield", "armor", "hull"):
|
||||
ehps["%sRepair" % type] = self.effectivify(fit, tankInfo["%sRepair" % type], type)
|
||||
|
||||
return ehps
|
||||
typeMap = {
|
||||
"passiveShield": "shield",
|
||||
"shieldRepair": "shield",
|
||||
"armorRepair": "armor",
|
||||
"armorRepairPreSpool": "armor",
|
||||
"armorRepairFullSpool": "armor",
|
||||
"hullRepair": "hull"}
|
||||
ereps = {}
|
||||
for field in tankInfo:
|
||||
if field in typeMap:
|
||||
ereps[field] = self.effectivify(fit, tankInfo[field], typeMap[field])
|
||||
return ereps
|
||||
|
||||
def effectivify(self, fit, amount, type):
|
||||
type = type if type != "hull" else ""
|
||||
|
||||
@@ -24,12 +24,13 @@ from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
|
||||
def __init__(self, item):
|
||||
@@ -65,8 +66,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
@@ -120,37 +121,67 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats()
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
|
||||
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1))
|
||||
self.__baseVolley = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed"
|
||||
cycleTime = self.getModifiedItemAttr(cycleAttr)
|
||||
dpsFactor = 1 / (cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def getRemoteReps(self, ignoreState=False):
|
||||
if self.amountActive <= 0 and not ignoreState:
|
||||
return (None, 0)
|
||||
if self.__baseRemoteReps is None:
|
||||
rrShield = self.getModifiedItemAttr("shieldBonus", 0)
|
||||
rrArmor = self.getModifiedItemAttr("armorDamageAmount", 0)
|
||||
rrHull = self.getModifiedItemAttr("structureDamageAmount", 0)
|
||||
if rrShield:
|
||||
rrType = "Shield"
|
||||
rrAmount = rrShield
|
||||
elif rrArmor:
|
||||
rrType = "Armor"
|
||||
rrAmount = rrArmor
|
||||
elif rrHull:
|
||||
rrType = "Hull"
|
||||
rrAmount = rrHull
|
||||
else:
|
||||
rrType = None
|
||||
rrAmount = 0
|
||||
if rrAmount:
|
||||
droneAmount = self.amount if ignoreState else self.amountActive
|
||||
rrAmount *= droneAmount / (self.cycleTime / 1000)
|
||||
self.__baseRemoteReps = (rrType, rrAmount)
|
||||
return self.__baseRemoteReps
|
||||
|
||||
def changeType(self, typeID):
|
||||
self.itemID = typeID
|
||||
self.init()
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage is True and self.amountActive > 0:
|
||||
if self.hasAmmo:
|
||||
attr = "missileLaunchDuration"
|
||||
getter = self.getModifiedChargeAttr
|
||||
else:
|
||||
attr = "speed"
|
||||
getter = self.getModifiedItemAttr
|
||||
|
||||
cycleTime = self.getModifiedItemAttr(attr)
|
||||
|
||||
volley = sum(
|
||||
[(getter("%sDamage" % d) or 0) * (1 - getattr(targetResists, "%sAmount" % d, 0)) for d in self.DAMAGE_TYPES])
|
||||
volley *= self.amountActive
|
||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
self.__volley = volley
|
||||
self.__dps = volley / (cycleTime / 1000.0)
|
||||
|
||||
return self.__dps, self.__volley
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -208,8 +239,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -26,6 +26,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.saveddata.module import Slot
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -87,8 +88,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
@@ -172,43 +172,88 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats()
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for ability in self.abilities:
|
||||
# Not passing resists here as we want to calculate and store base volley
|
||||
abilityVolley = ability.getVolley()
|
||||
em += abilityVolley.em
|
||||
therm += abilityVolley.thermal
|
||||
kin += abilityVolley.kinetic
|
||||
exp += abilityVolley.explosive
|
||||
self.__baseVolley = DmgTypes(em, therm, kin, exp)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.active and self.amountActive > 0:
|
||||
for ability in self.abilities:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
self.__dps += dps
|
||||
self.__volley += volley
|
||||
|
||||
# For forward compatability this assumes a fighter
|
||||
# can have more than 2 damaging abilities and/or
|
||||
# multiple that use charges.
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
constantDps = 0
|
||||
for ability in self.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.numShots == 0:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
constantDps += dps
|
||||
continue
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
self.__dps = max(constantDps, self.__dps * shortestActive / (shortestActive + longestReload))
|
||||
|
||||
return self.__dps, self.__volley
|
||||
def getDps(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Analyze cooldowns when reload is factored in
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
peakEm = 0
|
||||
peakTherm = 0
|
||||
peakKin = 0
|
||||
peakExp = 0
|
||||
steadyEm = 0
|
||||
steadyTherm = 0
|
||||
steadyKin = 0
|
||||
steadyExp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
# Peak dps
|
||||
peakEm += abilityDps.em
|
||||
peakTherm += abilityDps.thermal
|
||||
peakKin += abilityDps.kinetic
|
||||
peakExp += abilityDps.explosive
|
||||
# Infinite use - add to steady dps
|
||||
if ability.numShots == 0:
|
||||
steadyEm += abilityDps.em
|
||||
steadyTherm += abilityDps.thermal
|
||||
steadyKin += abilityDps.kinetic
|
||||
steadyExp += abilityDps.explosive
|
||||
else:
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
steadyDps = DmgTypes(steadyEm, steadyTherm, steadyKin, steadyExp)
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
peakDps = DmgTypes(peakEm, peakTherm, peakKin, peakExp)
|
||||
peakAdjustFactor = shortestActive / (shortestActive + longestReload)
|
||||
peakDpsAdjusted = DmgTypes(
|
||||
em=peakDps.em * peakAdjustFactor,
|
||||
thermal=peakDps.thermal * peakAdjustFactor,
|
||||
kinetic=peakDps.kinetic * peakAdjustFactor,
|
||||
explosive=peakDps.explosive * peakAdjustFactor)
|
||||
dps = max(steadyDps, peakDpsAdjusted, key=lambda d: d.total)
|
||||
return dps
|
||||
else:
|
||||
return steadyDps
|
||||
# Just sum all abilities when not taking reload into consideration
|
||||
else:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
em += abilityDps.em
|
||||
therm += abilityDps.thermal
|
||||
kin += abilityDps.kinetic
|
||||
exp += abilityDps.explosive
|
||||
return DmgTypes(em, therm, kin, exp)
|
||||
|
||||
@property
|
||||
def maxRange(self):
|
||||
@@ -251,8 +296,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -21,12 +21,12 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FighterAbility(object):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
DAMAGE_TYPES2 = ("EM", "Kin", "Exp", "Therm")
|
||||
|
||||
# We aren't able to get data on the charges that can be stored with fighters. So we hardcode that data here, keyed
|
||||
# with the fighter squadron role
|
||||
@@ -118,30 +118,38 @@ class FighterAbility(object):
|
||||
|
||||
return speed
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage and self.active:
|
||||
cycleTime = self.cycleTime
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or not self.active:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
em = self.fighter.getModifiedChargeAttr("emDamage", 0)
|
||||
therm = self.fighter.getModifiedChargeAttr("thermalDamage", 0)
|
||||
kin = self.fighter.getModifiedChargeAttr("kineticDamage", 0)
|
||||
exp = self.fighter.getModifiedChargeAttr("explosiveDamage", 0)
|
||||
else:
|
||||
em = self.fighter.getModifiedItemAttr("{}DamageEM".format(self.attrPrefix), 0)
|
||||
therm = self.fighter.getModifiedItemAttr("{}DamageTherm".format(self.attrPrefix), 0)
|
||||
kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix), 0)
|
||||
exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix), 0)
|
||||
dmgMult = self.fighter.amountActive * self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix), 1)
|
||||
volley = DmgTypes(
|
||||
em=em * dmgMult * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=therm * dmgMult * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=kin * dmgMult * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=exp * dmgMult * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
# bomb calcs
|
||||
volley = sum([(self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * (
|
||||
1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
||||
else:
|
||||
volley = sum(map(lambda d2, d:
|
||||
(self.fighter.getModifiedItemAttr(
|
||||
"{}Damage{}".format(self.attrPrefix, d2)) or 0) *
|
||||
(1 - getattr(targetResists, "{}Amount".format(d), 0)),
|
||||
self.DAMAGE_TYPES2, self.DAMAGE_TYPES))
|
||||
|
||||
volley *= self.fighter.amountActive
|
||||
volley *= self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix)) or 1
|
||||
self.__volley += volley
|
||||
self.__dps += volley / (cycleTime / 1000.0)
|
||||
|
||||
return self.__dps, self.__volley
|
||||
def getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
dpsFactor = 1 / (self.cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
|
||||
@@ -34,6 +34,7 @@ from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.character import Character
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.module import Module, State, Slot, Hardpoint
|
||||
from eos.utils.stats import DmgTypes
|
||||
from logbook import Logger
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -120,10 +121,11 @@ class Fit(object):
|
||||
def build(self):
|
||||
self.__extraDrains = []
|
||||
self.__ehp = None
|
||||
self.__weaponDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__remoteRepMap = {}
|
||||
self.__minerYield = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__sustainableTank = None
|
||||
@@ -135,12 +137,6 @@ class Fit(object):
|
||||
self.__capUsed = None
|
||||
self.__capRecharge = None
|
||||
self.__calculatedTargets = []
|
||||
self.__remoteReps = {
|
||||
"Armor" : None,
|
||||
"Shield" : None,
|
||||
"Hull" : None,
|
||||
"Capacitor": None,
|
||||
}
|
||||
self.factorReload = False
|
||||
self.boostsFits = set()
|
||||
self.gangBoosts = None
|
||||
@@ -154,9 +150,9 @@ class Fit(object):
|
||||
@targetResists.setter
|
||||
def targetResists(self, targetResists):
|
||||
self.__targetResists = targetResists
|
||||
self.__weaponDPS = None
|
||||
self.__weaponVolley = None
|
||||
self.__droneDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
|
||||
@property
|
||||
@@ -277,41 +273,31 @@ class Fit(object):
|
||||
def projectedFighters(self):
|
||||
return self.__projectedFighters
|
||||
|
||||
@property
|
||||
def weaponDPS(self):
|
||||
if self.__weaponDPS is None:
|
||||
self.calculateWeaponStats()
|
||||
def getWeaponDps(self, spoolOptions=None):
|
||||
if spoolOptions not in self.__weaponDpsMap:
|
||||
self.calculateWeaponDmgStats(spoolOptions)
|
||||
return self.__weaponDpsMap[spoolOptions]
|
||||
|
||||
return self.__weaponDPS
|
||||
def getWeaponVolley(self, spoolOptions=None):
|
||||
if spoolOptions not in self.__weaponVolleyMap:
|
||||
self.calculateWeaponDmgStats(spoolOptions)
|
||||
return self.__weaponVolleyMap[spoolOptions]
|
||||
|
||||
@property
|
||||
def weaponVolley(self):
|
||||
if self.__weaponVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
def getDroneDps(self):
|
||||
if self.__droneDps is None:
|
||||
self.calculateDroneDmgStats()
|
||||
return self.__droneDps
|
||||
|
||||
return self.__weaponVolley
|
||||
|
||||
@property
|
||||
def droneDPS(self):
|
||||
if self.__droneDPS is None:
|
||||
self.calculateWeaponStats()
|
||||
|
||||
return self.__droneDPS
|
||||
|
||||
@property
|
||||
def droneVolley(self):
|
||||
def getDroneVolley(self):
|
||||
if self.__droneVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
|
||||
self.calculateDroneDmgStats()
|
||||
return self.__droneVolley
|
||||
|
||||
@property
|
||||
def totalDPS(self):
|
||||
return self.droneDPS + self.weaponDPS
|
||||
def getTotalDps(self, spoolOptions=None):
|
||||
return self.getDroneDps() + self.getWeaponDps(spoolOptions=spoolOptions)
|
||||
|
||||
@property
|
||||
def totalVolley(self):
|
||||
return self.droneVolley + self.weaponVolley
|
||||
def getTotalVolley(self, spoolOptions=None):
|
||||
return self.getDroneVolley() + self.getWeaponVolley(spoolOptions=spoolOptions)
|
||||
|
||||
@property
|
||||
def minerYield(self):
|
||||
@@ -409,12 +395,13 @@ class Fit(object):
|
||||
|
||||
def clear(self, projected=False, command=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDPS = None
|
||||
self.__weaponDpsMap = {}
|
||||
self.__weaponVolleyMap = {}
|
||||
self.__remoteRepMap = {}
|
||||
self.__minerYield = None
|
||||
self.__weaponVolley = None
|
||||
self.__effectiveSustainableTank = None
|
||||
self.__sustainableTank = None
|
||||
self.__droneDPS = None
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
self.__droneYield = None
|
||||
self.__ehp = None
|
||||
@@ -426,9 +413,6 @@ class Fit(object):
|
||||
self.ecmProjectedStr = 1
|
||||
# self.commandBonuses = {}
|
||||
|
||||
for remoterep_type in self.__remoteReps:
|
||||
self.__remoteReps[remoterep_type] = None
|
||||
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
@@ -1032,11 +1016,11 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
def pgUsed(self):
|
||||
return self.getItemAttrOnlineSum(self.modules, "power")
|
||||
return round(self.getItemAttrOnlineSum(self.modules, "power"), 2)
|
||||
|
||||
@property
|
||||
def cpuUsed(self):
|
||||
return self.getItemAttrOnlineSum(self.modules, "cpu")
|
||||
return round(self.getItemAttrOnlineSum(self.modules, "cpu"), 2)
|
||||
|
||||
@property
|
||||
def droneBandwidthUsed(self):
|
||||
@@ -1151,149 +1135,6 @@ class Fit(object):
|
||||
|
||||
return self.__capRecharge
|
||||
|
||||
@property
|
||||
def sustainableTank(self):
|
||||
if self.__sustainableTank is None:
|
||||
self.calculateSustainableTank()
|
||||
|
||||
return self.__sustainableTank
|
||||
|
||||
def calculateSustainableTank(self, effective=True):
|
||||
if self.__sustainableTank is None:
|
||||
if self.capStable and not self.factorReload:
|
||||
sustainable = {
|
||||
"armorRepair" : self.extraAttributes["armorRepair"],
|
||||
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||
"hullRepair" : self.extraAttributes["hullRepair"]
|
||||
}
|
||||
else:
|
||||
sustainable = {}
|
||||
|
||||
repairers = []
|
||||
# Map a repairer type to the attribute it uses
|
||||
groupAttrMap = {
|
||||
"Shield Booster": "shieldBonus",
|
||||
"Ancillary Shield Booster": "shieldBonus",
|
||||
"Remote Shield Booster": "shieldBonus",
|
||||
"Ancillary Remote Shield Booster": "shieldBonus",
|
||||
|
||||
"Armor Repair Unit": "armorDamageAmount",
|
||||
"Ancillary Armor Repairer": "armorDamageAmount",
|
||||
"Remote Armor Repairer": "armorDamageAmount",
|
||||
"Ancillary Remote Armor Repairer": "armorDamageAmount",
|
||||
|
||||
"Hull Repair Unit": "structureDamageAmount",
|
||||
"Remote Hull Repairer": "structureDamageAmount",
|
||||
}
|
||||
# Map repairer type to attribute
|
||||
groupStoreMap = {
|
||||
"Shield Booster": "shieldRepair",
|
||||
"Remote Shield Booster": "shieldRepair",
|
||||
"Ancillary Shield Booster": "shieldRepair",
|
||||
"Ancillary Remote Shield Booster": "shieldRepair",
|
||||
|
||||
"Armor Repair Unit": "armorRepair",
|
||||
"Remote Armor Repairer": "armorRepair",
|
||||
"Ancillary Armor Repairer": "armorRepair",
|
||||
"Ancillary Remote Armor Repairer": "armorRepair",
|
||||
|
||||
"Hull Repair Unit": "hullRepair",
|
||||
"Remote Hull Repairer": "hullRepair",
|
||||
}
|
||||
|
||||
capUsed = self.capUsed
|
||||
for attr in ("shieldRepair", "armorRepair", "hullRepair"):
|
||||
sustainable[attr] = self.extraAttributes[attr]
|
||||
dict = self.extraAttributes.getAfflictions(attr)
|
||||
if self in dict:
|
||||
for mod, _, amount, used in dict[self]:
|
||||
if not used:
|
||||
continue
|
||||
if mod.projected is False:
|
||||
usesCap = True
|
||||
try:
|
||||
if mod.capUse:
|
||||
capUsed -= mod.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])
|
||||
sustainable[attr] -= amount / (cycleTime / 1000.0)
|
||||
repairers.append(mod)
|
||||
# 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
|
||||
else:
|
||||
multiplier = 1
|
||||
sustainable[attr] -= amount * multiplier / (cycleTime / 1000.0)
|
||||
repairers.append(mod)
|
||||
# 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
|
||||
else:
|
||||
reloadtime = 0.0
|
||||
offdutycycle = reloadtime / ((max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||
sustainable[attr] -= amount * offdutycycle / (cycleTime / 1000.0)
|
||||
|
||||
# Sort repairers by efficiency. We want to use the most efficient repairers first
|
||||
repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr(
|
||||
groupAttrMap[_mod.item.group.name]) * (_mod.getModifiedItemAttr(
|
||||
"chargedArmorDamageMultiplier") or 1) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True)
|
||||
|
||||
# Loop through every module until we're above peak recharge
|
||||
# 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:
|
||||
if capUsed > totalPeakRecharge:
|
||||
break
|
||||
|
||||
if self.factorReload and mod.charge:
|
||||
reloadtime = mod.reloadTime
|
||||
else:
|
||||
reloadtime = 0.0
|
||||
|
||||
cycleTime = mod.rawCycleTime
|
||||
capPerSec = mod.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])
|
||||
# Add the sustainable amount
|
||||
|
||||
if not mod.charge:
|
||||
sustainable[groupStoreMap[mod.item.group.name]] += sustainability * amount / (
|
||||
cycleTime / 1000.0)
|
||||
else:
|
||||
if mod.charge.name == "Nanite Repair Paste":
|
||||
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
||||
else:
|
||||
multiplier = 1
|
||||
ondutycycle = (max(mod.numShots, 1) * cycleTime) / (
|
||||
(max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||
sustainable[groupStoreMap[
|
||||
mod.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
|
||||
cycleTime / 1000.0)
|
||||
|
||||
capUsed += capPerSec
|
||||
|
||||
sustainable["passiveShield"] = self.calculateShieldRecharge()
|
||||
self.__sustainableTank = sustainable
|
||||
|
||||
return self.__sustainableTank
|
||||
|
||||
def calculateCapRecharge(self, percent=PEAK_RECHARGE):
|
||||
capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
|
||||
rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
|
||||
@@ -1377,92 +1218,27 @@ class Fit(object):
|
||||
self.__capStable = True
|
||||
self.__capState = 100
|
||||
|
||||
@property
|
||||
def remoteReps(self):
|
||||
force_recalc = False
|
||||
for remote_type in self.__remoteReps:
|
||||
if self.__remoteReps[remote_type] is None:
|
||||
force_recalc = True
|
||||
break
|
||||
def getRemoteReps(self, spoolOptions=None):
|
||||
if spoolOptions not in self.__remoteRepMap:
|
||||
remoteReps = {}
|
||||
|
||||
if force_recalc is False:
|
||||
return self.__remoteReps
|
||||
for module in self.modules:
|
||||
rrType, rrAmount = module.getRemoteReps(spoolOptions=spoolOptions)
|
||||
if rrType:
|
||||
if rrType not in remoteReps:
|
||||
remoteReps[rrType] = 0
|
||||
remoteReps[rrType] += rrAmount
|
||||
|
||||
# We are rerunning the recalcs. Explicitly set to 0 to make sure we don't duplicate anything and correctly set
|
||||
# all values to 0.
|
||||
for remote_type in self.__remoteReps:
|
||||
self.__remoteReps[remote_type] = 0
|
||||
for drone in self.drones:
|
||||
rrType, rrAmount = drone.getRemoteReps()
|
||||
if rrType:
|
||||
if rrType not in remoteReps:
|
||||
remoteReps[rrType] = 0
|
||||
remoteReps[rrType] += rrAmount
|
||||
|
||||
for stuff in chain(self.modules, self.drones):
|
||||
if stuff.item:
|
||||
if stuff.item.ID == 10250:
|
||||
pass
|
||||
remote_type = None
|
||||
self.__remoteRepMap[spoolOptions] = remoteReps
|
||||
|
||||
# Only apply the charged multiplier if we have a charge in our ancil reppers (#1135)
|
||||
if stuff.charge:
|
||||
modifier = stuff.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
||||
else:
|
||||
modifier = 1
|
||||
|
||||
if isinstance(stuff, Module) and (stuff.isEmpty or stuff.state < State.ACTIVE):
|
||||
continue
|
||||
elif isinstance(stuff, Drone):
|
||||
# drones don't have fueled charges, so simply override modifier with the amount of drones active
|
||||
modifier = stuff.amountActive
|
||||
|
||||
# Covert cycleTime to seconds
|
||||
duration = stuff.cycleTime / 1000
|
||||
|
||||
# Skip modules with no duration.
|
||||
if not duration:
|
||||
continue
|
||||
|
||||
remote_module_groups = {
|
||||
"Remote Armor Repairer" : "Armor",
|
||||
"Ancillary Remote Armor Repairer": "Armor",
|
||||
"Remote Hull Repairer" : "Hull",
|
||||
"Remote Shield Booster" : "Shield",
|
||||
"Ancillary Remote Shield Booster": "Shield",
|
||||
"Remote Capacitor Transmitter" : "Capacitor",
|
||||
}
|
||||
|
||||
module_group = stuff.item.group.name
|
||||
|
||||
if module_group in remote_module_groups:
|
||||
remote_type = remote_module_groups[module_group]
|
||||
elif not isinstance(stuff, Drone):
|
||||
# Module isn't in our list of remote rep modules, bail
|
||||
continue
|
||||
|
||||
if remote_type == "Hull":
|
||||
hp = stuff.getModifiedItemAttr("structureDamageAmount", 0)
|
||||
elif remote_type == "Armor":
|
||||
hp = stuff.getModifiedItemAttr("armorDamageAmount", 0)
|
||||
elif remote_type == "Shield":
|
||||
hp = stuff.getModifiedItemAttr("shieldBonus", 0)
|
||||
elif remote_type == "Capacitor":
|
||||
hp = stuff.getModifiedItemAttr("powerTransferAmount", 0)
|
||||
else:
|
||||
droneShield = stuff.getModifiedItemAttr("shieldBonus", 0)
|
||||
droneArmor = stuff.getModifiedItemAttr("armorDamageAmount", 0)
|
||||
droneHull = stuff.getModifiedItemAttr("structureDamageAmount", 0)
|
||||
if droneShield:
|
||||
remote_type = "Shield"
|
||||
hp = droneShield
|
||||
elif droneArmor:
|
||||
remote_type = "Armor"
|
||||
hp = droneArmor
|
||||
elif droneHull:
|
||||
remote_type = "Hull"
|
||||
hp = droneHull
|
||||
else:
|
||||
hp = 0
|
||||
|
||||
if hp > 0 and duration > 0:
|
||||
self.__remoteReps[remote_type] += (hp * modifier) / duration
|
||||
|
||||
return self.__remoteReps
|
||||
return self.__remoteRepMap[spoolOptions]
|
||||
|
||||
@property
|
||||
def hp(self):
|
||||
@@ -1485,11 +1261,14 @@ class Fit(object):
|
||||
|
||||
@property
|
||||
def tank(self):
|
||||
hps = {"passiveShield": self.calculateShieldRecharge()}
|
||||
for type in ("shield", "armor", "hull"):
|
||||
hps["%sRepair" % type] = self.extraAttributes["%sRepair" % type]
|
||||
|
||||
return hps
|
||||
reps = {
|
||||
"passiveShield": self.calculateShieldRecharge(),
|
||||
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||
"armorRepair": self.extraAttributes["armorRepair"],
|
||||
"armorRepairPreSpool": self.extraAttributes["armorRepairPreSpool"],
|
||||
"armorRepairFullSpool": self.extraAttributes["armorRepairFullSpool"],
|
||||
"hullRepair": self.extraAttributes["hullRepair"]}
|
||||
return reps
|
||||
|
||||
@property
|
||||
def effectiveTank(self):
|
||||
@@ -1497,24 +1276,153 @@ class Fit(object):
|
||||
if self.damagePattern is None:
|
||||
ehps = self.tank
|
||||
else:
|
||||
ehps = self.damagePattern.calculateEffectiveTank(self, self.extraAttributes)
|
||||
ehps = self.damagePattern.calculateEffectiveTank(self, self.tank)
|
||||
|
||||
self.__effectiveTank = ehps
|
||||
|
||||
return self.__effectiveTank
|
||||
|
||||
@property
|
||||
def sustainableTank(self):
|
||||
if self.__sustainableTank is None:
|
||||
self.calculateSustainableTank()
|
||||
|
||||
return self.__sustainableTank
|
||||
|
||||
@property
|
||||
def effectiveSustainableTank(self):
|
||||
if self.__effectiveSustainableTank is None:
|
||||
if self.damagePattern is None:
|
||||
eshps = self.sustainableTank
|
||||
tank = self.sustainableTank
|
||||
else:
|
||||
eshps = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
||||
|
||||
self.__effectiveSustainableTank = eshps
|
||||
|
||||
tank = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)
|
||||
self.__effectiveSustainableTank = tank
|
||||
return self.__effectiveSustainableTank
|
||||
|
||||
def calculateSustainableTank(self):
|
||||
if self.__sustainableTank is None:
|
||||
sustainable = {
|
||||
"passiveShield": self.calculateShieldRecharge(),
|
||||
"shieldRepair": self.extraAttributes["shieldRepair"],
|
||||
"armorRepair": self.extraAttributes["armorRepair"],
|
||||
"armorRepairPreSpool": self.extraAttributes["armorRepairPreSpool"],
|
||||
"armorRepairFullSpool": self.extraAttributes["armorRepairFullSpool"],
|
||||
"hullRepair": self.extraAttributes["hullRepair"]}
|
||||
if not self.capStable or self.factorReload:
|
||||
# Map a local repairer type to the attribute it uses
|
||||
groupAttrMap = {
|
||||
"Shield Booster": "shieldBonus",
|
||||
"Ancillary Shield Booster": "shieldBonus",
|
||||
"Armor Repair Unit": "armorDamageAmount",
|
||||
"Ancillary Armor Repairer": "armorDamageAmount",
|
||||
"Hull Repair Unit": "structureDamageAmount"}
|
||||
# Map local repairer type to tank type
|
||||
groupStoreMap = {
|
||||
"Shield Booster": "shieldRepair",
|
||||
"Ancillary Shield Booster": "shieldRepair",
|
||||
"Armor Repair Unit": "armorRepair",
|
||||
"Ancillary Armor Repairer": "armorRepair",
|
||||
"Hull Repair Unit": "hullRepair"}
|
||||
repairers = []
|
||||
localAdjustment = {"shieldRepair": 0, "armorRepair": 0, "hullRepair": 0}
|
||||
capUsed = self.capUsed
|
||||
for tankType in localAdjustment:
|
||||
dict = self.extraAttributes.getAfflictions(tankType)
|
||||
if self in dict:
|
||||
for mod, _, amount, used in dict[self]:
|
||||
if not used:
|
||||
continue
|
||||
if mod.projected:
|
||||
continue
|
||||
if mod.item.group.name not in groupAttrMap:
|
||||
continue
|
||||
usesCap = True
|
||||
try:
|
||||
if mod.capUse:
|
||||
capUsed -= mod.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])
|
||||
localAdjustment[tankType] -= amount / (cycleTime / 1000.0)
|
||||
repairers.append(mod)
|
||||
# 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
|
||||
else:
|
||||
multiplier = 1
|
||||
localAdjustment[tankType] -= amount * multiplier / (cycleTime / 1000.0)
|
||||
repairers.append(mod)
|
||||
# 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
|
||||
else:
|
||||
reloadtime = 0.0
|
||||
offdutycycle = reloadtime / ((max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||
localAdjustment[tankType] -= amount * offdutycycle / (cycleTime / 1000.0)
|
||||
|
||||
# Sort repairers by efficiency. We want to use the most efficient repairers first
|
||||
repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr(
|
||||
groupAttrMap[_mod.item.group.name]) * (_mod.getModifiedItemAttr(
|
||||
"chargedArmorDamageMultiplier") or 1) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True)
|
||||
|
||||
# Loop through every module until we're above peak recharge
|
||||
# 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:
|
||||
if capUsed > totalPeakRecharge:
|
||||
break
|
||||
|
||||
if self.factorReload and mod.charge:
|
||||
reloadtime = mod.reloadTime
|
||||
else:
|
||||
reloadtime = 0.0
|
||||
|
||||
cycleTime = mod.rawCycleTime
|
||||
capPerSec = mod.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])
|
||||
# Add the sustainable amount
|
||||
if not mod.charge:
|
||||
localAdjustment[groupStoreMap[mod.item.group.name]] += sustainability * amount / (
|
||||
cycleTime / 1000.0)
|
||||
else:
|
||||
if mod.charge.name == "Nanite Repair Paste":
|
||||
multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1
|
||||
else:
|
||||
multiplier = 1
|
||||
ondutycycle = (max(mod.numShots, 1) * cycleTime) / (
|
||||
(max(mod.numShots, 1) * cycleTime) + reloadtime)
|
||||
localAdjustment[groupStoreMap[
|
||||
mod.item.group.name]] += sustainability * amount * ondutycycle * multiplier / (
|
||||
cycleTime / 1000.0)
|
||||
|
||||
capUsed += capPerSec
|
||||
sustainable["shieldRepair"] += localAdjustment["shieldRepair"]
|
||||
sustainable["armorRepair"] += localAdjustment["armorRepair"]
|
||||
sustainable["armorRepairPreSpool"] += localAdjustment["armorRepair"]
|
||||
sustainable["armorRepairFullSpool"] += localAdjustment["armorRepair"]
|
||||
sustainable["hullRepair"] += localAdjustment["hullRepair"]
|
||||
|
||||
self.__sustainableTank = sustainable
|
||||
|
||||
return self.__sustainableTank
|
||||
|
||||
def calculateLockTime(self, radius):
|
||||
scanRes = self.ship.getModifiedItemAttr("scanResolution")
|
||||
if scanRes is not None and scanRes > 0:
|
||||
@@ -1537,30 +1445,30 @@ class Fit(object):
|
||||
self.__minerYield = minerYield
|
||||
self.__droneYield = droneYield
|
||||
|
||||
def calculateWeaponStats(self):
|
||||
weaponDPS = 0
|
||||
droneDPS = 0
|
||||
weaponVolley = 0
|
||||
droneVolley = 0
|
||||
def calculateWeaponDmgStats(self, spoolOptions):
|
||||
weaponVolley = DmgTypes(0, 0, 0, 0)
|
||||
weaponDps = DmgTypes(0, 0, 0, 0)
|
||||
|
||||
for mod in self.modules:
|
||||
dps, volley = mod.damageStats(self.targetResists)
|
||||
weaponDPS += dps
|
||||
weaponVolley += volley
|
||||
weaponVolley += mod.getVolley(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||
weaponDps += mod.getDps(spoolOptions=spoolOptions, targetResists=self.targetResists)
|
||||
|
||||
self.__weaponVolleyMap[spoolOptions] = weaponVolley
|
||||
self.__weaponDpsMap[spoolOptions] = weaponDps
|
||||
|
||||
def calculateDroneDmgStats(self):
|
||||
droneVolley = DmgTypes(0, 0, 0, 0)
|
||||
droneDps = DmgTypes(0, 0, 0, 0)
|
||||
|
||||
for drone in self.drones:
|
||||
dps, volley = drone.damageStats(self.targetResists)
|
||||
droneDPS += dps
|
||||
droneVolley += volley
|
||||
droneVolley += drone.getVolley(targetResists=self.targetResists)
|
||||
droneDps += drone.getDps(targetResists=self.targetResists)
|
||||
|
||||
for fighter in self.fighters:
|
||||
dps, volley = fighter.damageStats(self.targetResists)
|
||||
droneDPS += dps
|
||||
droneVolley += volley
|
||||
droneVolley += fighter.getVolley(targetResists=self.targetResists)
|
||||
droneDps += fighter.getDps(targetResists=self.targetResists)
|
||||
|
||||
self.__weaponDPS = weaponDPS
|
||||
self.__weaponVolley = weaponVolley
|
||||
self.__droneDPS = droneDPS
|
||||
self.__droneDps = droneDps
|
||||
self.__droneVolley = droneVolley
|
||||
|
||||
@property
|
||||
|
||||
@@ -28,6 +28,9 @@ from eos.enum import Enum
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.spoolSupport import calculateSpoolup, resolveSpoolOptions
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -95,7 +98,6 @@ class Hardpoint(Enum):
|
||||
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
||||
|
||||
@@ -168,9 +170,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.__charge and self.__charge.category.name != "Charge":
|
||||
self.__charge = None
|
||||
|
||||
self.__dps = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__volley = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
@@ -247,8 +249,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if chargeVolume is None or containerCapacity is None:
|
||||
charges = 0
|
||||
else:
|
||||
charges = floor(containerCapacity / chargeVolume)
|
||||
return int(charges)
|
||||
charges = int(floatUnerr(containerCapacity / chargeVolume))
|
||||
return charges
|
||||
|
||||
@property
|
||||
def numShots(self):
|
||||
@@ -411,35 +413,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
self.__itemModifiedAttributes.clear()
|
||||
|
||||
def damageStats(self, targetResists):
|
||||
if self.__dps is None:
|
||||
self.__dps = 0
|
||||
self.__volley = 0
|
||||
|
||||
if not self.isEmpty and self.state >= State.ACTIVE:
|
||||
if self.charge:
|
||||
func = self.getModifiedChargeAttr
|
||||
else:
|
||||
func = self.getModifiedItemAttr
|
||||
|
||||
volley = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
# Disintegrator-specific ramp-up multiplier
|
||||
volley *= (self.getModifiedItemAttr("damageMultiplierBonusMax") or 0) + 1
|
||||
if volley:
|
||||
cycleTime = self.cycleTime
|
||||
# Some weapons repeat multiple times in one cycle (think doomsdays)
|
||||
# Get the number of times it fires off
|
||||
weaponDoT = max(
|
||||
self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1),
|
||||
1
|
||||
)
|
||||
|
||||
self.__volley = volley
|
||||
self.__dps = (volley * weaponDoT) / (cycleTime / 1000.0)
|
||||
|
||||
return self.__dps, self.__volley
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -459,13 +432,110 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
return self.__miningyield
|
||||
|
||||
@property
|
||||
def dps(self):
|
||||
return self.damageStats(None)[0]
|
||||
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier", 1)
|
||||
self.__baseVolley = DmgTypes(
|
||||
em=(dmgGetter("emDamage", 0)) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage", 0)) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult)
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
spoolBoost = calculateSpoolup(
|
||||
self.getModifiedItemAttr("damageMultiplierBonusMax", 0),
|
||||
self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0),
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||
spoolMultiplier = 1 + spoolBoost
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
@property
|
||||
def volley(self):
|
||||
return self.damageStats(None)[1]
|
||||
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Some weapons repeat multiple times in one cycle (bosonic doomsdays). Get the number of times it fires off
|
||||
volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1)
|
||||
dpsFactor = volleysPerCycle / (self.cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def getRemoteReps(self, spoolOptions=None, ignoreState=False):
|
||||
if self.isEmpty or (self.state < State.ACTIVE and not ignoreState):
|
||||
return None, 0
|
||||
|
||||
def getBaseRemoteReps(module):
|
||||
remoteModuleGroups = {
|
||||
"Remote Armor Repairer": "Armor",
|
||||
"Ancillary Remote Armor Repairer": "Armor",
|
||||
"Mutadaptive Remote Armor Repairer": "Armor",
|
||||
"Remote Hull Repairer": "Hull",
|
||||
"Remote Shield Booster": "Shield",
|
||||
"Ancillary Remote Shield Booster": "Shield",
|
||||
"Remote Capacitor Transmitter": "Capacitor"}
|
||||
rrType = remoteModuleGroups.get(module.item.group.name, None)
|
||||
if not rrType:
|
||||
return None, 0
|
||||
if rrType == "Hull":
|
||||
rrAmount = module.getModifiedItemAttr("structureDamageAmount", 0)
|
||||
elif rrType == "Armor":
|
||||
rrAmount = module.getModifiedItemAttr("armorDamageAmount", 0)
|
||||
elif rrType == "Shield":
|
||||
rrAmount = module.getModifiedItemAttr("shieldBonus", 0)
|
||||
elif rrType == "Capacitor":
|
||||
rrAmount = module.getModifiedItemAttr("powerTransferAmount", 0)
|
||||
else:
|
||||
return None, 0
|
||||
if rrAmount:
|
||||
rrAmount *= 1 / (self.cycleTime / 1000)
|
||||
if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
|
||||
rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
||||
|
||||
return rrType, rrAmount
|
||||
|
||||
if self.__baseRemoteReps is None:
|
||||
self.__baseRemoteReps = getBaseRemoteReps(self)
|
||||
|
||||
rrType, rrAmount = self.__baseRemoteReps
|
||||
|
||||
if rrType and rrAmount and self.item.group.name == "Mutadaptive Remote Armor Repairer":
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
spoolBoost = calculateSpoolup(
|
||||
self.getModifiedItemAttr("repairMultiplierBonusMax", 0),
|
||||
self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0),
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
|
||||
rrAmount *= (1 + spoolBoost)
|
||||
|
||||
return rrType, rrAmount
|
||||
|
||||
def getSpoolData(self, spoolOptions=None):
|
||||
weaponMultMax = self.getModifiedItemAttr("damageMultiplierBonusMax", 0)
|
||||
weaponMultPerCycle = self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0)
|
||||
if weaponMultMax and weaponMultPerCycle:
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
_, spoolCycles, spoolTime = calculateSpoolup(
|
||||
weaponMultMax, weaponMultPerCycle,
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)
|
||||
return spoolCycles, spoolTime
|
||||
rrMultMax = self.getModifiedItemAttr("repairMultiplierBonusMax", 0)
|
||||
rrMultPerCycle = self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0)
|
||||
if rrMultMax and rrMultPerCycle:
|
||||
spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
|
||||
_, spoolCycles, spoolTime = calculateSpoolup(
|
||||
rrMultMax, rrMultPerCycle,
|
||||
self.rawCycleTime / 1000, spoolType, spoolAmount)
|
||||
return spoolCycles, spoolTime
|
||||
return 0, 0
|
||||
|
||||
@property
|
||||
def reloadTime(self):
|
||||
@@ -718,9 +788,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__baseVolley = None
|
||||
self.__baseRemoteReps = None
|
||||
self.__miningyield = None
|
||||
self.__volley = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
self.__chargeCycles = None
|
||||
|
||||
@@ -18,24 +18,30 @@
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
import time
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
import time
|
||||
from enum import IntEnum, unique
|
||||
|
||||
from logbook import Logger
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
@unique
|
||||
class PriceStatus(IntEnum):
|
||||
notFetched = 0
|
||||
success = 1
|
||||
fail = 2
|
||||
notSupported = 3
|
||||
|
||||
|
||||
class Price(object):
|
||||
def __init__(self, typeID):
|
||||
self.typeID = typeID
|
||||
self.time = 0
|
||||
self.__price = 0
|
||||
self.failed = None
|
||||
|
||||
@reconstructor
|
||||
def init(self):
|
||||
self.__item = None
|
||||
self.status = PriceStatus.notFetched
|
||||
|
||||
@property
|
||||
def isValid(self):
|
||||
@@ -43,7 +49,10 @@ class Price(object):
|
||||
|
||||
@property
|
||||
def price(self):
|
||||
return self.__price or 0.0
|
||||
if self.status != PriceStatus.success:
|
||||
return 0
|
||||
else:
|
||||
return self.__price or 0
|
||||
|
||||
@price.setter
|
||||
def price(self, price):
|
||||
|
||||
@@ -29,14 +29,16 @@ pyfalog = Logger(__name__)
|
||||
|
||||
class Ship(ItemAttrShortcut, HandledItem):
|
||||
EXTRA_ATTRIBUTES = {
|
||||
"armorRepair" : 0,
|
||||
"hullRepair" : 0,
|
||||
"shieldRepair" : 0,
|
||||
"maxActiveDrones" : 0,
|
||||
"armorRepair": 0,
|
||||
"armorRepairPreSpool": 0,
|
||||
"armorRepairFullSpool": 0,
|
||||
"hullRepair": 0,
|
||||
"shieldRepair": 0,
|
||||
"maxActiveDrones": 0,
|
||||
"maxTargetsLockedFromSkills": 2,
|
||||
"droneControlRange" : 20000,
|
||||
"cloaked" : False,
|
||||
"siege" : False
|
||||
"droneControlRange": 20000,
|
||||
"cloaked": False,
|
||||
"siege": False
|
||||
# We also have speedLimit for Entosis Link, but there seems to be an
|
||||
# issue with naming it exactly "speedLimit" due to unknown reasons.
|
||||
# Regardless, we don't have to put it here anyways - it will come up
|
||||
|
||||
0
eos/utils/__init__.py
Normal file
0
eos/utils/__init__.py
Normal file
26
eos/utils/float.py
Normal file
26
eos/utils/float.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Sometimes use of floats may lead to undesirable results, e.g.
|
||||
int(2.3 / 0.1) = 22.
|
||||
We cannot afford to use different number representations (e.g. representations
|
||||
provided by decimal or fraction modules), thus consequences are worked around by
|
||||
this module.
|
||||
"""
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
|
||||
# As we will be rounding numbers after operations (which introduce higher error
|
||||
# than base float representation error), we need to keep less significant
|
||||
# numbers than for single float number w/o operations
|
||||
keepDigits = int(sys.float_info.dig / 2)
|
||||
|
||||
|
||||
def floatUnerr(value):
|
||||
"""Round possible float number error, killing some precision in process."""
|
||||
if value == 0:
|
||||
return value
|
||||
# Find round factor, taking into consideration that we want to keep at least
|
||||
# predefined amount of significant digits
|
||||
roundFactor = int(keepDigits - math.ceil(math.log10(abs(value))))
|
||||
return round(value, roundFactor)
|
||||
70
eos/utils/spoolSupport.py
Normal file
70
eos/utils/spoolSupport.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
from enum import IntEnum, unique
|
||||
|
||||
from eos.utils.float import floatUnerr
|
||||
|
||||
|
||||
SpoolOptions = namedtuple('SpoolOptions', ('spoolType', 'spoolAmount', 'force'))
|
||||
|
||||
|
||||
@unique
|
||||
class SpoolType(IntEnum):
|
||||
SCALE = 0 # [0..1]
|
||||
TIME = 1 # Expressed via time in seconds since spool up started
|
||||
CYCLES = 2 # Expressed in amount of cycles since spool up started
|
||||
|
||||
|
||||
def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAmount):
|
||||
"""
|
||||
Calculate damage multiplier increment based on passed parameters. Module cycle time
|
||||
is specified in seconds.
|
||||
|
||||
Returns spoolup value, amount of cycles to reach it and time to reach it.
|
||||
"""
|
||||
if not modMaxValue or not modStepValue:
|
||||
return 0, 0, 0
|
||||
if spoolType == SpoolType.SCALE:
|
||||
cycles = int(floatUnerr(spoolAmount * modMaxValue / modStepValue))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
elif spoolType == SpoolType.TIME:
|
||||
cycles = min(int(floatUnerr(spoolAmount / modCycleTime)), int(floatUnerr(modMaxValue / modStepValue)))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
elif spoolType == SpoolType.CYCLES:
|
||||
cycles = min(int(spoolAmount), int(floatUnerr(modMaxValue / modStepValue)))
|
||||
return cycles * modStepValue, cycles, cycles * modCycleTime
|
||||
else:
|
||||
return 0, 0, 0
|
||||
|
||||
|
||||
def resolveSpoolOptions(spoolOptions, module):
|
||||
# Rely on passed options if they are forcing us to do so
|
||||
if spoolOptions is not None and spoolOptions.force:
|
||||
return spoolOptions.spoolType, spoolOptions.spoolAmount
|
||||
# If we're not forced to use options and module has options set, prefer on-module values
|
||||
elif module is not None and module.spoolType is not None:
|
||||
return module.spoolType, module.spoolAmount
|
||||
# Otherwise - rely on passed options
|
||||
elif spoolOptions is not None:
|
||||
return spoolOptions.spoolType, spoolOptions.spoolAmount
|
||||
else:
|
||||
return None, None
|
||||
70
eos/utils/stats.py
Normal file
70
eos/utils/stats.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class DmgTypes:
|
||||
"""Container for damage data stats."""
|
||||
|
||||
def __init__(self, em, thermal, kinetic, explosive):
|
||||
self.em = em
|
||||
self.thermal = thermal
|
||||
self.kinetic = kinetic
|
||||
self.explosive = explosive
|
||||
self._calcTotal()
|
||||
|
||||
# Iterator is needed to support tuple-style unpacking
|
||||
def __iter__(self):
|
||||
yield self.em
|
||||
yield self.thermal
|
||||
yield self.kinetic
|
||||
yield self.explosive
|
||||
yield self.total
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DmgTypes):
|
||||
return NotImplemented
|
||||
return all((
|
||||
self.em == other.em,
|
||||
self.thermal == other.thermal,
|
||||
self.kinetic == other.kinetic,
|
||||
self.explosive == other.explosive,
|
||||
self.total == other.total))
|
||||
|
||||
def __bool__(self):
|
||||
return any((
|
||||
self.em, self.thermal, self.kinetic,
|
||||
self.explosive, self.total))
|
||||
|
||||
def _calcTotal(self):
|
||||
self.total = self.em + self.thermal + self.kinetic + self.explosive
|
||||
|
||||
def __add__(self, other):
|
||||
return type(self)(
|
||||
em=self.em + other.em,
|
||||
thermal=self.thermal + other.thermal,
|
||||
kinetic=self.kinetic + other.kinetic,
|
||||
explosive=self.explosive + other.explosive)
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.em += other.em
|
||||
self.thermal += other.thermal
|
||||
self.kinetic += other.kinetic
|
||||
self.explosive += other.explosive
|
||||
self._calcTotal()
|
||||
return self
|
||||
Reference in New Issue
Block a user