Merge branch 'master' of github.com-shyadow:pyfa-org/Pyfa into master

This commit is contained in:
shyadow
2022-02-07 22:12:20 +01:00
185 changed files with 60129 additions and 19622 deletions

View File

@@ -27,7 +27,8 @@ import re
import sqlite3
import sys
from sqlalchemy import or_
import sqlalchemy.orm
from sqlalchemy import or_, and_
# todo: need to set the EOS language to en, becasuse this assumes it's being run within an English context
@@ -146,6 +147,7 @@ def update_db():
# Nearly useless and clutter search results too much
elif (
row['typeName_en-us'].startswith('Limited Synth ') or
row['typeName_en-us'].startswith('Expired ') or
row['typeName_en-us'].endswith(' Filament') and (
"'Needlejack'" not in row['typeName_en-us'] and
"'Devana'" not in row['typeName_en-us'] and
@@ -595,8 +597,14 @@ def update_db():
attr.value = 4.0
for item in eos.db.gamedata_session.query(eos.gamedata.Item).filter(or_(
eos.gamedata.Item.name.like('%abyssal%'),
eos.gamedata.Item.name.like('%mutated%')
eos.gamedata.Item.name.like('%mutated%'),
# Drifter weapons are published for some reason
eos.gamedata.Item.name.in_(('Lux Kontos', 'Lux Xiphos'))
)).all():
if 'Asteroid Mining Crystal' in item.name:
continue
if 'Mutated Drone Specialization' in item.name:
continue
item.published = False
for x in [
@@ -606,6 +614,148 @@ def update_db():
print ('Removing Category: {}'.format(cat.name))
eos.db.gamedata_session.delete(cat)
def _hardcodeAttribs(typeID, attrMap):
for attrName, value in attrMap.items():
try:
attr = eos.db.gamedata_session.query(eos.gamedata.Attribute).filter(and_(
eos.gamedata.Attribute.name == attrName, eos.gamedata.Attribute.typeID == typeID)).one()
except sqlalchemy.orm.exc.NoResultFound:
attrInfo = eos.db.gamedata_session.query(eos.gamedata.AttributeInfo).filter(eos.gamedata.AttributeInfo.name == attrName).one()
attr = eos.gamedata.Attribute()
attr.ID = attrInfo.ID
attr.typeID = typeID
attr.value = value
eos.db.gamedata_session.add(attr)
else:
attr.value = value
def _hardcodeEffects(typeID, effectMap):
item = eos.db.gamedata_session.query(eos.gamedata.Item).filter(eos.gamedata.Item.ID == typeID).one()
item.effects.clear()
for effectID, effectName in effectMap.items():
effect = eos.gamedata.Effect()
effect.effectID = effectID
effect.effectName = effectName
item.effects[effectName] = effect
def hardcodeRaiju():
attrMap = {
'hp': 600,
'capacity': 220,
'mass': 987000,
'volume': 27289,
'agility': 3.1,
'emDamageResonance': 1 - 0.33,
'thermalDamageResonance': 1 - 0.33,
'kineticDamageResonance': 1 - 0.33,
'explosiveDamageResonance': 1 - 0.33,
'armorHP': 700,
'armorEmDamageResonance': 1 - 0.5,
'armorThermalDamageResonance': 1 - 0.8625,
'armorKineticDamageResonance': 1 - 0.625,
'armorExplosiveDamageResonance': 1 - 0.1,
'shieldCapacity': 850,
'shieldRechargeRate': 625000,
'shieldEmDamageResonance': 1 - 0.15,
'shieldThermalDamageResonance': 1 - 0.8,
'shieldKineticDamageResonance': 1 - 0.7,
'shieldExplosiveDamageResonance': 1 - 0.5,
'energyWarfareResistance': 1,
'weaponDisruptionResistance': 1,
'capacitorCapacity': 400,
'rechargeRate': 195000,
'maxTargetRange': 70000,
'maxLockedTargets': 7,
'signatureRadius': 32,
'scanResolution': 650,
'scanRadarStrength': 0,
'scanLadarStrength': 0,
'scanMagnetometricStrength': 0,
'scanGravimetricStrength': 13,
'maxVelocity': 440,
'warpSpeedMultiplier': 5.5,
'cpuOutput': 247,
'powerOutput': 38,
'upgradeCapacity': 400,
'launcherSlotsLeft': 3,
'hiSlots': 3,
'medSlots': 6,
'lowSlots': 3,
'rigSlots': 3,
'rigSize': 1}
effectMap = {
100100: 'pyfaCustomRaijuPointRange',
100101: 'pyfaCustomRaijuPointCap',
100102: 'pyfaCustomRaijuDampStr',
100103: 'pyfaCustomRaijuDampCap',
100104: 'pyfaCustomRaijuMissileDmg',
100105: 'pyfaCustomRaijuMissileFlightTime',
100106: 'pyfaCustomRaijuMissileFlightVelocity'}
_hardcodeAttribs(60765, attrMap)
_hardcodeEffects(60765, effectMap)
def hardcodeLaelaps():
attrMap = {
'hp': 2300,
'capacity': 420,
'droneCapacity': 25,
'droneBandwidth': 25,
'mass': 10298000,
'volume': 101000,
'agility': 0.48,
'emDamageResonance': 1 - 0.33,
'thermalDamageResonance': 1 - 0.33,
'kineticDamageResonance': 1 - 0.33,
'explosiveDamageResonance': 1 - 0.33,
'armorHP': 2730,
'armorEmDamageResonance': 1 - 0.5,
'armorThermalDamageResonance': 1 - 0.8625,
'armorKineticDamageResonance': 1 - 0.625,
'armorExplosiveDamageResonance': 1 - 0.1,
'shieldCapacity': 3540,
'shieldRechargeRate': 1250000,
'shieldEmDamageResonance': 1 - 0.15,
'shieldThermalDamageResonance': 1 - 0.8,
'shieldKineticDamageResonance': 1 - 0.7,
'shieldExplosiveDamageResonance': 1 - 0.5,
'energyWarfareResistance': 1,
'weaponDisruptionResistance': 1,
'capacitorCapacity': 1550,
'rechargeRate': 315000,
'maxTargetRange': 80000,
'maxLockedTargets': 7,
'signatureRadius': 135,
'scanResolution': 300,
'scanRadarStrength': 0,
'scanLadarStrength': 0,
'scanMagnetometricStrength': 0,
'scanGravimetricStrength': 21,
'maxVelocity': 230,
'warpSpeedMultiplier': 4.5,
'cpuOutput': 560,
'powerOutput': 900,
'upgradeCapacity': 400,
'launcherSlotsLeft': 5,
'hiSlots': 7,
'medSlots': 5,
'lowSlots': 4,
'rigSlots': 3,
'rigSize': 2}
effectMap = {
100200: 'pyfaCustomLaelapsWdfgRange',
100201: 'pyfaCustomLaelapsMissileReload',
100202: 'pyfaCustomLaelapsMissileDamage',
100203: 'pyfaCustomLaelapsMissileRof',
100204: 'pyfaCustomLaelapsShieldResists',
100205: 'pyfaCustomLaelapsMissileFlightTime',
100206: 'pyfaCustomLaelapsWdfgSigPenalty',
100207: 'pyfaCustomLaelapsMissileFlightVelocity'}
_hardcodeAttribs(60764, attrMap)
_hardcodeEffects(60764, effectMap)
hardcodeRaiju()
hardcodeLaelaps()
eos.db.gamedata_session.commit()
eos.db.gamedata_engine.execute('VACUUM')

View File

@@ -0,0 +1,68 @@
"""
Migration 46
- Mining crystal changes
"""
CONVERSIONS = {
60276: ( # Simple Asteroid Mining Crystal Type A I
18066, # Veldspar Mining Crystal I
18062, # Scordite Mining Crystal I
18060, # Pyroxeres Mining Crystal I
18058, # Plagioclase Mining Crystal I
),
60281: ( # Simple Asteroid Mining Crystal Type A II
18618, # Veldspar Mining Crystal II
18616, # Scordite Mining Crystal II
18614, # Pyroxeres Mining Crystal II
18612, # Plagioclase Mining Crystal II
),
60285: ( # Coherent Asteroid Mining Crystal Type A I
18056, # Omber Mining Crystal I
18052, # Kernite Mining Crystal I
18050, # Jaspet Mining Crystal I
18048, # Hemorphite Mining Crystal I
18046, # Hedbergite Mining Crystal I
),
60288: ( # Coherent Asteroid Mining Crystal Type A II
18610, # Omber Mining Crystal II
18604, # Jaspet Mining Crystal II
18606, # Kernite Mining Crystal II
18600, # Hedbergite Mining Crystal II
18602, # Hemorphite Mining Crystal II
),
60291: ( # Variegated Asteroid Mining Crystal Type A I
18044, # Gneiss Mining Crystal I
18042, # Dark Ochre Mining Crystal I
18040, # Crokite Mining Crystal I
),
60294: ( # Variegated Asteroid Mining Crystal Type A II
18598, # Gneiss Mining Crystal II
18596, # Dark Ochre Mining Crystal II
18594, # Crokite Mining Crystal II
),
60297: ( # Complex Asteroid Mining Crystal Type A I
18038, # Bistot Mining Crystal I
18036, # Arkonor Mining Crystal I
18064, # Spodumain Mining Crystal I
),
60300: ( # Complex Asteroid Mining Crystal Type A II
18592, # Bistot Mining Crystal II
18590, # Arkonor Mining Crystal II
18624, # Spodumain Mining Crystal II
),
}
def upgrade(saveddata_engine):
# Convert modules
for replacement_item, list in CONVERSIONS.items():
for retired_item in list:
saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?',
(replacement_item, retired_item))
saveddata_engine.execute('UPDATE "modules" SET "baseItemID" = ? WHERE "baseItemID" = ?',
(replacement_item, retired_item))
saveddata_engine.execute('UPDATE "modules" SET "chargeID" = ? WHERE "chargeID" = ?',
(replacement_item, retired_item))
saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?',
(replacement_item, retired_item))

File diff suppressed because it is too large Load Diff

View File

@@ -71,30 +71,30 @@ class ItemAttrShortcut:
def getModifiedItemAttr(self, key, default=0):
return_value = self.itemModifiedAttributes.get(key)
return return_value or default
return return_value if return_value is not None else default
def getModifiedItemAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.itemModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value or default
return return_value if return_value is not None else default
def getItemBaseAttrValue(self, key, default=0):
return_value = self.itemModifiedAttributes.getOriginal(key)
return return_value or default
return return_value if return_value is not None else default
class ChargeAttrShortcut:
def getModifiedChargeAttr(self, key, default=0):
return_value = self.chargeModifiedAttributes.get(key)
return return_value or default
return return_value if return_value is not None else default
def getModifiedChargeAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.chargeModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value or default
return return_value if return_value is not None else default
def getChargeBaseAttrValue(self, key, default=0):
return_value = self.chargeModifiedAttributes.getOriginal(key)
return return_value or default
return return_value if return_value is not None else default
class ModifiedAttributeDict(MutableMapping):

View File

@@ -80,7 +80,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
self.__charge = None
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__itemModifiedAttributes = ModifiedAttributeDict()
self.__itemModifiedAttributes.original = self._item.attributes
self.__itemModifiedAttributes.overrides = self._item.overrides
@@ -89,9 +90,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
self._mutaLoadMutators(mutatorClass=MutatorDrone)
self.__itemModifiedAttributes.mutators = self.mutators
# pheonix todo: check the attribute itself, not the modified. this will always return 0 now.
chargeID = self.getModifiedItemAttr("entityMissileTypeID", None)
if chargeID is not None:
if chargeID:
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
@@ -129,8 +129,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
cycleTime = self.getModifiedItemAttr("missileLaunchDuration", 0)
else:
for attr in ("speed", "duration", "durationHighisGood"):
cycleTime = self.getModifiedItemAttr(attr, None)
if cycleTime is not None:
cycleTime = self.getModifiedItemAttr(attr)
if cycleTime:
break
if cycleTime is None:
return 0
@@ -237,35 +237,49 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def getCycleParameters(self, reloadOverride=None):
cycleTime = self.cycleTime
if cycleTime == 0:
if not cycleTime:
return None
return CycleInfo(self.cycleTime, 0, math.inf, False)
@property
def miningStats(self):
if self.__miningyield is None:
if self.mines is True and self.amountActive > 0:
getter = self.getModifiedItemAttr
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
def getMiningYPS(self, ignoreState=False):
if not ignoreState and self.amountActive <= 0:
return 0
if self.__miningYield is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningYield
return self.__miningyield
def getMiningWPS(self, ignoreState=False):
if not ignoreState and self.amountActive <= 0:
return 0
if self.__miningWaste is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningWaste
def __calculateMining(self):
if self.mines is True:
getter = self.getModifiedItemAttr
cycleParams = self.getCycleParameters()
if cycleParams is None:
yps = 0
else:
cycleTime = cycleParams.averageTime
yield_ = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amount
yps = yield_ / (cycleTime / 1000.0)
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
wps = yps * max(0, min(1, wasteChance / 100)) * wasteMult
return yps, wps
else:
return 0, 0
@property
def maxRange(self):
attrs = ("shieldTransferRange", "powerTransferRange",
"energyDestabilizationRange", "empFieldRange",
"ecmBurstRange", "maxRange")
"ecmBurstRange", "maxRange", "ECMRangeOptimal")
for attr in attrs:
maxRange = self.getModifiedItemAttr(attr, None)
if maxRange is not None:
maxRange = self.getModifiedItemAttr(attr)
if maxRange:
return maxRange
if self.charge is not None:
delay = self.getModifiedChargeAttr("explosionDelay")
@@ -280,8 +294,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def falloff(self):
attrs = ("falloff", "falloffEffectiveness")
for attr in attrs:
falloff = self.getModifiedItemAttr(attr, None)
if falloff is not None:
falloff = self.getModifiedItemAttr(attr)
if falloff:
return falloff
@validates("ID", "itemID", "chargeID", "amount", "amountActive")
@@ -302,7 +316,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def clear(self):
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.itemModifiedAttributes.clear()
self.chargeModifiedAttributes.clear()
@@ -375,8 +390,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, Mu
def fits(self, fit):
fitDroneGroupLimits = set()
for i in range(1, 3):
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i, None)
if groneGrp is not None:
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i)
if groneGrp:
fitDroneGroupLimits.add(int(groneGrp))
if len(fitDroneGroupLimits) == 0:
return True

View File

@@ -21,7 +21,7 @@ import datetime
import time
from copy import deepcopy
from itertools import chain
from math import floor, log, sqrt
from math import ceil, log, sqrt
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
@@ -139,9 +139,11 @@ class Fit:
self.__weaponVolleyMap = {}
self.__remoteRepMap = {}
self.__minerYield = None
self.__droneYield = None
self.__minerWaste = None
self.__droneWaste = None
self.__droneDps = None
self.__droneVolley = None
self.__droneYield = None
self.__sustainableTank = None
self.__effectiveSustainableTank = None
self.__effectiveTank = None
@@ -365,26 +367,44 @@ class Fit:
@property
def minerYield(self):
if self.__minerYield is None:
self.calculateMiningStats()
self.calculatemining()
return self.__minerYield
@property
def minerWaste(self):
if self.__minerWaste is None:
self.calculatemining()
return self.__minerWaste
@property
def droneYield(self):
if self.__droneYield is None:
self.calculateMiningStats()
self.calculatemining()
return self.__droneYield
@property
def droneWaste(self):
if self.__droneWaste is None:
self.calculatemining()
return self.__droneWaste
@property
def totalYield(self):
return self.droneYield + self.minerYield
@property
def totalWaste(self):
return self.droneWaste + self.minerWaste
@property
def maxTargets(self):
maxTargets = min(self.extraAttributes["maxTargetsLockedFromSkills"],
self.ship.getModifiedItemAttr("maxLockedTargets"))
return floor(floatUnerr(maxTargets))
return ceil(floatUnerr(maxTargets))
@property
def maxTargetRange(self):
@@ -491,11 +511,13 @@ class Fit:
self.__weaponVolleyMap = {}
self.__remoteRepMap = {}
self.__minerYield = None
self.__droneYield = None
self.__minerWaste = None
self.__droneWaste = None
self.__effectiveSustainableTank = None
self.__sustainableTank = None
self.__droneDps = None
self.__droneVolley = None
self.__droneYield = None
self.__ehp = None
self.__calculated = False
self.__capStable = None
@@ -1627,18 +1649,23 @@ class Fit:
else:
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
def calculateMiningStats(self):
def calculatemining(self):
minerYield = 0
minerWaste = 0
droneYield = 0
droneWaste = 0
for mod in self.modules:
minerYield += mod.miningStats
minerYield += mod.getMiningYPS()
minerWaste += mod.getMiningWPS()
for drone in self.drones:
droneYield += drone.miningStats
droneYield += drone.getMiningYPS()
droneWaste += drone.getMiningWPS()
self.__minerYield = minerYield
self.__minerWaste = minerWaste
self.__droneYield = droneYield
self.__droneWaste = droneWaste
def calculateWeaponDmgStats(self, spoolOptions):
weaponVolley = DmgTypes(0, 0, 0, 0)

View File

@@ -124,7 +124,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None
@@ -306,10 +307,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
"shipScanRange", "surveyScanRange")
maxRange = None
for attr in attrs:
maxRange = self.getModifiedItemAttr(attr, None)
if maxRange is not None:
maxRange = self.getModifiedItemAttr(attr)
if maxRange:
break
if maxRange is not None:
if maxRange:
if 'burst projector' in self.item.name.lower():
maxRange -= self.owner.ship.getModifiedItemAttr("radius")
return maxRange
@@ -371,8 +372,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
def falloff(self):
attrs = ("falloffEffectiveness", "falloff", "shipScanFalloff")
for attr in attrs:
falloff = self.getModifiedItemAttr(attr, None)
if falloff is not None:
falloff = self.getModifiedItemAttr(attr)
if falloff:
return falloff
@property
@@ -409,28 +410,39 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
self.__itemModifiedAttributes.clear()
@property
def miningStats(self):
if self.__miningyield is None:
if self.isEmpty:
self.__miningyield = 0
else:
if self.state >= FittingModuleState.ACTIVE:
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
"miningAmount") or 0
if volley:
cycleParams = self.getCycleParameters()
if cycleParams is None:
self.__miningyield = 0
else:
cycleTime = cycleParams.averageTime
self.__miningyield = volley / (cycleTime / 1000.0)
else:
self.__miningyield = 0
else:
self.__miningyield = 0
def getMiningYPS(self, ignoreState=False):
if self.isEmpty:
return 0
if not ignoreState and self.state < FittingModuleState.ACTIVE:
return 0
if self.__miningYield is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningYield
return self.__miningyield
def getMiningWPS(self, ignoreState=False):
if self.isEmpty:
return 0
if not ignoreState and self.state < FittingModuleState.ACTIVE:
return 0
if self.__miningWaste is None:
self.__miningYield, self.__miningWaste = self.__calculateMining()
return self.__miningWaste
def __calculateMining(self):
yield_ = self.getModifiedItemAttr("miningAmount")
if yield_:
cycleParams = self.getCycleParameters()
if cycleParams is None:
yps = 0
else:
cycleTime = cycleParams.averageTime
yps = yield_ / (cycleTime / 1000.0)
else:
yps = 0
wasteChance = self.getModifiedItemAttr("miningWasteProbability")
wasteMult = self.getModifiedItemAttr("miningWastedVolumeMultiplier")
wps = yps * max(0, min(1, wasteChance / 100)) * wasteMult
return yps, wps
def isDealingDamage(self, ignoreState=False):
volleyParams = self.getVolleyParameters(ignoreState=ignoreState)
@@ -642,6 +654,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
"""
slot = self.slot
if slot is None:
return False
if fit.getSlotsFree(slot) <= (0 if self.owner != fit else -1):
return False
@@ -680,8 +694,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
return False
# Check max group fitted
max = self.getModifiedItemAttr("maxGroupFitted", None)
if max is not None:
max = self.getModifiedItemAttr("maxGroupFitted")
if max:
current = 0 # if self.owner != fit else -1 # Disabled, see #1278
for mod in fit.modules:
if (mod.item and mod.item.groupID == self.item.groupID and
@@ -734,7 +748,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
# Check if the local module is over it's max limit; if it's not, we're fine
maxGroupOnline = self.getModifiedItemAttr("maxGroupOnline", None)
maxGroupActive = self.getModifiedItemAttr("maxGroupActive", None)
if maxGroupOnline is None and maxGroupActive is None and projectedOnto is None:
if not maxGroupOnline and not maxGroupActive and projectedOnto is None:
return True
# Following is applicable only to local modules, we do not want to limit projected
@@ -750,11 +764,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
currOnline += 1
if mod.state >= FittingModuleState.ACTIVE:
currActive += 1
if maxGroupOnline is not None and currOnline > maxGroupOnline:
if maxGroupOnline and currOnline > maxGroupOnline:
if maxState is None or maxState > FittingModuleState.OFFLINE:
maxState = FittingModuleState.OFFLINE
break
if maxGroupActive is not None and currActive > maxGroupActive:
if maxGroupActive and currActive > maxGroupActive:
if maxState is None or maxState > FittingModuleState.ONLINE:
maxState = FittingModuleState.ONLINE
return True if maxState is None else maxState
@@ -792,7 +806,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
chargeGroup = charge.groupID
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is None:
if not itemChargeGroup:
continue
if itemChargeGroup == chargeGroup:
return True
@@ -803,7 +817,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
validCharges = set()
for i in range(5):
itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
if itemChargeGroup is not None:
if itemChargeGroup:
g = eos.db.getGroup(int(itemChargeGroup), eager="items.attributes")
if g is None:
continue
@@ -865,7 +879,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut, M
def clear(self):
self.__baseVolley = None
self.__baseRRAmount = None
self.__miningyield = None
self.__miningYield = None
self.__miningWaste = None
self.__reloadTime = None
self.__reloadForce = None
self.__chargeCycles = None

View File

@@ -1,5 +1,6 @@
# noinspection PyPackageRequirements
from collections import OrderedDict
# noinspection PyPackageRequirements
import wx
import gui.fitCommands as cmd
@@ -25,8 +26,20 @@ class ChangeModuleAmmo(ContextMenuCombined):
'thermal': _t('Thermal'),
'explosive': _t('Explosive'),
'kinetic': _t('Kinetic'),
'mixed': _t('Mixed')
}
'mixed': _t('Mixed')}
self.oreChargeCatTrans = OrderedDict([
('a1', _t('Asteroid Simple')),
('a2', _t('Asteroid Coherent')),
('a3', _t('Asteroid Variegated')),
('a4', _t('Asteroid Complex')),
('a5', _t('Asteroid Abyssal')),
('a6', _t('Asteroid Mercoxit')),
('r4', _t('Moon Ubiquitous')),
('r8', _t('Moon Common')),
('r16', _t('Moon Uncommon')),
('r32', _t('Moon Rare')),
('r64', _t('Moon Exceptional')),
('misc', _t('Misc'))])
def display(self, callingWindow, srcContext, mainItem, selection):
if srcContext not in ('fittingModule', 'projectedModule'):
@@ -115,6 +128,24 @@ class ChangeModuleAmmo(ContextMenuCombined):
self._addSeparator(subMenu, _t('More Damage'))
for menuItem in menuItems:
menu.Append(menuItem)
elif modType == 'miner':
menuItems = []
for catHandle, catLabel in self.oreChargeCatTrans.items():
charges = chargeDict.get(catHandle)
if not charges:
continue
if len(charges) == 1:
menuItems.append(self._addCharge(rootMenu if msw else menu, charges[0]))
else:
menuItem = wx.MenuItem(menu, wx.ID_ANY, catLabel)
menuItems.append(menuItem)
subMenu = wx.Menu()
subMenu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
menuItem.SetSubMenu(subMenu)
for charge in charges:
subMenu.Append(self._addCharge(rootMenu if msw else subMenu, charge))
for menuItem in menuItems:
menu.Append(menuItem)
elif modType == 'general':
for charge in chargeDict['general']:
menu.Append(self._addCharge(rootMenu if msw else menu, charge))

View File

@@ -59,7 +59,8 @@ AttrGroupDict = {
"agility",
"droneCapacity",
"droneBandwidth",
"specialOreHoldCapacity",
"generalMiningHoldCapacity",
"specialIceHoldCapacity",
"specialGasHoldCapacity",
"specialMineralHoldCapacity",
"specialSalvageHoldCapacity",

View File

@@ -1,16 +1,19 @@
import csv
import config
from enum import IntEnum
# noinspection PyPackageRequirements
import wx
import wx.lib.agw.hypertreelist
import config
import gui
from gui import globalEvents as GE
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount, roundDec
from enum import IntEnum
from gui.builtinItemStatsViews.attributeGrouping import *
from gui.utils.numberFormatter import formatAmount, roundDec
from service.const import GuiAttrGroup
_t = wx.GetTranslation
@@ -25,6 +28,8 @@ class ItemParams(wx.Panel):
wx.Panel.__init__(self, parent, size=(1000, 1000))
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.paramList = wx.lib.agw.hypertreelist.HyperTreeList(self, wx.ID_ANY,
@@ -37,6 +42,8 @@ class ItemParams(wx.Panel):
self.toggleView = AttributeView.NORMAL
self.stuff = stuff
self.item = item
self.isStuffItem = stuff is not None and item is not None and getattr(stuff, 'item', None) == item
self.isStuffCharge = stuff is not None and item is not None and getattr(stuff, 'charge', None) == item
self.attrInfo = {}
self.attrValues = {}
self._fetchValues()
@@ -71,6 +78,10 @@ class ItemParams(wx.Panel):
self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode)
self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats)
self.mainFrame.Bind(GE.ITEM_CHANGED_INPLACE, self.OnUpdateStuff)
def OnWindowClose(self):
self.mainFrame.Unbind(GE.ITEM_CHANGED_INPLACE)
def _fetchValues(self):
if self.stuff is None:
@@ -78,12 +89,12 @@ class ItemParams(wx.Panel):
self.attrValues.clear()
self.attrInfo.update(self.item.attributes)
self.attrValues.update(self.item.attributes)
elif self.stuff.item == self.item:
elif self.isStuffItem:
self.attrInfo.clear()
self.attrValues.clear()
self.attrInfo.update(self.stuff.item.attributes)
self.attrValues.update(self.stuff.itemModifiedAttributes)
elif self.stuff.charge == self.item:
elif self.isStuffCharge:
self.attrInfo.clear()
self.attrValues.clear()
self.attrInfo.update(self.stuff.charge.attributes)
@@ -171,6 +182,10 @@ class ItemParams(wx.Panel):
]
)
def OnUpdateStuff(self, event):
if self.stuff is event.old:
self.stuff = event.new
def SetupImageList(self):
self.imageList.RemoveAll()
@@ -282,9 +297,9 @@ class ItemParams(wx.Panel):
valDefault = getattr(info, "value", None) # Get default value from attribute
if self.stuff is not None:
# if it's a stuff, overwrite default (with fallback to current value)
if self.stuff.item == self.item:
if self.isStuffItem:
valDefault = self.stuff.getItemBaseAttrValue(attr, valDefault)
elif self.stuff.charge == self.item:
elif self.isStuffCharge:
valDefault = self.stuff.getChargeBaseAttrValue(attr, valDefault)
valueDefault = valDefault if valDefault is not None else att
@@ -357,3 +372,4 @@ class ItemParams(wx.Panel):
fvalue = value
unitSuffix = f' {unit}' if unit is not None else ''
return f'{fvalue}{unitSuffix}'

View File

@@ -130,21 +130,33 @@ class MiningYieldViewFull(StatsView):
def refreshPanel(self, fit):
# If we did anything intresting, we'd update our labels to reflect the new fit's stats here
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, 3, 0, 0, "%s m\u00B3/s", None),
("labelFullminingyieldDrone", lambda: fit.droneYield, 3, 0, 0, "%s m\u00B3/s", None),
("labelFullminingyieldTotal", lambda: fit.totalYield, 3, 0, 0, "%s m\u00B3/s", None))
stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, lambda: fit.minerWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
("labelFullminingyieldDrone", lambda: fit.droneYield, lambda: fit.droneWaste, 3, 0, 0, "{}{} m\u00B3/s", None),
("labelFullminingyieldTotal", lambda: fit.totalYield, lambda: fit.totalWaste, 3, 0, 0, "{}{} m\u00B3/s", None))
counter = 0
for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
def processValue(value):
value = value() if fit is not None else 0
value = value if value is not None else 0
if self._cachedValues[counter] != value:
valueStr = formatAmount(value, prec, lowest, highest)
label.SetLabel(valueFormat % valueStr)
tipStr = "Mining Yield per second ({0} per hour)".format(formatAmount(value * 3600, 3, 0, 3))
label.SetToolTip(wx.ToolTip(tipStr))
self._cachedValues[counter] = value
return value
counter = 0
for labelName, yieldValue, wasteValue, prec, lowest, highest, valueFormat, altFormat in stats:
label = getattr(self, labelName)
yieldValue = processValue(yieldValue)
wasteValue = processValue(wasteValue)
if self._cachedValues[counter] != (yieldValue, wasteValue):
yps = formatAmount(yieldValue, prec, lowest, highest)
yph = formatAmount(yieldValue * 3600, prec, lowest, highest)
wps = formatAmount(wasteValue, prec, lowest, highest)
wph = formatAmount(wasteValue * 3600, prec, lowest, highest)
wasteSuffix = '\u02b7' if wasteValue > 0 else ''
label.SetLabel(valueFormat.format(yps, wasteSuffix))
tipLines = []
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(yps, yph))
if wasteValue > 0:
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(wps, wph))
label.SetToolTip(wx.ToolTip('\n'.join(tipLines)))
self._cachedValues[counter] = (yieldValue, wasteValue)
counter += 1
self.panel.Layout()
self.headerPanel.Layout()

View File

@@ -119,10 +119,11 @@ class TargetingMiscViewMinimal(StatsView):
("specialMediumShipHoldCapacity", _t("Medium ship hold")),
("specialLargeShipHoldCapacity", _t("Large ship hold")),
("specialIndustrialShipHoldCapacity", _t("Industrial ship hold")),
("specialOreHoldCapacity", _t("Ore hold")),
("generalMiningHoldCapacity", _t("Mining hold")),
("specialIceHoldCapacity", _t("Ice hold")),
("specialGasHoldCapacity", _t("Gas hold")),
("specialMineralHoldCapacity", _t("Mineral hold")),
("specialMaterialBayCapacity", _t("Material bay")),
("specialGasHoldCapacity", _t("Gas hold")),
("specialSalvageHoldCapacity", _t("Salvage hold")),
("specialCommandCenterHoldCapacity", _t("Command center hold")),
("specialPlanetaryCommoditiesHoldCapacity", _t("Planetary goods hold")),
@@ -139,10 +140,11 @@ class TargetingMiscViewMinimal(StatsView):
"specialMediumShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMediumShipHoldCapacity"),
"specialLargeShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialLargeShipHoldCapacity"),
"specialIndustrialShipHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialIndustrialShipHoldCapacity"),
"specialOreHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialOreHoldCapacity"),
"generalMiningHoldCapacity": lambda: fit.ship.getModifiedItemAttr("generalMiningHoldCapacity"),
"specialIceHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialIceHoldCapacity"),
"specialGasHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialGasHoldCapacity"),
"specialMineralHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialMineralHoldCapacity"),
"specialMaterialBayCapacity": lambda: fit.ship.getModifiedItemAttr("specialMaterialBayCapacity"),
"specialGasHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialGasHoldCapacity"),
"specialSalvageHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialSalvageHoldCapacity"),
"specialCommandCenterHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialCommandCenterHoldCapacity"),
"specialPlanetaryCommoditiesHoldCapacity": lambda: fit.ship.getModifiedItemAttr("specialPlanetaryCommoditiesHoldCapacity"),

View File

@@ -80,7 +80,7 @@ class AttributeDisplay(ViewColumn):
else:
attr = mod.getAttribute(self.info.name)
if attr is None:
if not attr:
return ""
if self.info.name == "volume":

View File

@@ -537,14 +537,24 @@ class Miscellanea(ViewColumn):
text = "{0}m".format(formatAmount(optimalSig, 3, 0, 3))
tooltip = "Optimal signature radius"
return text, tooltip
elif itemGroup in ("Frequency Mining Laser", "Strip Miner", "Mining Laser", "Gas Cloud Harvester", "Mining Drone"):
miningAmount = stuff.getModifiedItemAttr("specialtyMiningAmount") or stuff.getModifiedItemAttr("miningAmount")
cycleTime = getattr(stuff, 'cycleTime', stuff.getModifiedItemAttr("duration"))
if not miningAmount or not cycleTime:
elif itemGroup in ("Frequency Mining Laser", "Strip Miner", "Mining Laser", "Gas Cloud Scoops", "Mining Drone", "Gas Cloud Harvesters"):
yps = stuff.getMiningYPS(ignoreState=True)
if not yps:
return "", None
minePerSec = (float(miningAmount) * 1000 / cycleTime)
text = "{0} m3/s".format(formatAmount(minePerSec, 3, 0, 3))
tooltip = "Mining Yield per second ({0} per hour)".format(formatAmount(minePerSec * 3600, 3, 0, 3))
yph = yps * 3600
wps = stuff.getMiningWPS(ignoreState=True)
wph = wps * 3600
textParts = []
textParts.append(formatAmount(yps, 3, 0, 3))
tipLines = []
tipLines.append("{} m\u00B3 mining yield per second ({} m\u00B3 per hour)".format(
formatAmount(yps, 3, 0, 3), formatAmount(yph, 3, 0, 3)))
if wps > 0:
textParts.append(formatAmount(wps, 3, 0, 3))
tipLines.append("{} m\u00B3 mining waste per second ({} m\u00B3 per hour)".format(
formatAmount(wps, 3, 0, 3), formatAmount(wph, 3, 0, 3)))
text = '{} m\u00B3/s'.format('+'.join(textParts))
tooltip = '\n'.join(tipLines)
return text, tooltip
elif itemGroup == "Logistic Drone":
rpsData = stuff.getRemoteReps(ignoreState=True)
@@ -679,7 +689,7 @@ class Miscellanea(ViewColumn):
formatAmount(itemArmorResistanceShiftHardenerKin, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerExp, 3, 0, 3),
)
tooltip = "Resistances Shifted to Damage Profile:\n{0}% EM | {1}% Therm | {2}% Kin | {3}% Exp".format(
tooltip = "Resistances shifted to damage profile:\n{0}% EM | {1}% Therm | {2}% Kin | {3}% Exp".format(
formatAmount(itemArmorResistanceShiftHardenerEM, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerTherm, 3, 0, 3),
formatAmount(itemArmorResistanceShiftHardenerKin, 3, 0, 3),
@@ -693,96 +703,80 @@ class Miscellanea(ViewColumn):
text = "{}s".format(formatAmount(duration / 1000, 3, 0, 0))
tooltip = "Scan duration"
return text, tooltip
elif itemGroup == "Command Burst":
# Text and tooltip are empty if there is no charge.
text = ""
tooltip = ""
buff_value = stuff.getModifiedItemAttr('warfareBuff1Value')
buff_id = stuff.getModifiedChargeAttr('warfareBuff1ID')
if buff_id == 10: # Shield Burst: Shield Harmonizing: Shield Resistance
# minus buff value because ingame shows positive value
text = f"{-buff_value:.1f}%"
tooltip = "Shield Resistance Bonus"
elif buff_id == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
text = f"{buff_value:+.1f}%"
tooltip = "Shield Repair Modules: Duration & Capacictor-use bonus"
elif buff_id == 12: # Shield Burst: Shield Extension: Shield HP
text = f"{buff_value:.1f}%"
tooltip = "Shield HP Bonus"
elif buff_id == 13: # Armor Burst: Armor Energizing: Armor Resistance
# minus buff value because ingame shows positive value
text = f"{-buff_value:.1f}%"
tooltip = "Armor Resistance Bonus"
elif buff_id == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
text = f"{buff_value:+.1f}%"
tooltip = "Armor Repair Modules: Duration & Capacitor-use bonus"
elif buff_id == 15: # Armor Burst: Armor Reinforcement: Armor HP
text = f"{buff_value:.1f}%"
tooltip = "Armor HP Bonus"
elif buff_id == 16: # Information Burst: Sensor Optimization: Scan Resolution
text = f"{buff_value:.1f}%"
tooltip = "Scan Resolution bonus"
elif buff_id == 26: # Information Burst: Sensor Optimization: Targeting Range
text += f" | {buff_value:.1f}%"
tooltip += " | Targeting Range bonus"
elif buff_id == 17: # Information Burst: Electronic Superiority: EWAR Range and Strength
text = f"{buff_value:.1f}%"
tooltip = "Electronic Warfare modules: Range and Strength bonus"
elif buff_id == 18: # Information Burst: Electronic Hardening: Sensor Strength
text = f"{buff_value:.1f}%"
tooltip = "Sensor Strength bonus"
buff2_value = stuff.getModifiedItemAttr('warfareBuff2Value')
# Information Burst: Electronic Hardening: RSD/RWD Resistance
text += f" | {buff2_value:+.1f}%"
tooltip += " | Remote Sensor Dampener / Remote Weapon Disruption Resistance bonus"
elif buff_id == 20: # Skirmish Burst: Evasive Maneuvers: Signature Radius
text = f"{buff_value:+.1f}%"
tooltip = "Signature Radius bonus"
buff2_value = stuff.getModifiedItemAttr('warfareBuff2Value')
# Skirmish Burst: Evasive Maneuvers: Agility
# minus the buff value because we want Agility as shown ingame, not inertia modifier
text += f" | {-buff2_value:.1f}%"
tooltip += " | Agility bonus"
elif buff_id == 21: # Skirmish Burst: Interdiction Maneuvers: Tackle Range
text = f"{buff_value:.1f}%"
tooltip = "Propulsion disruption module range bonus"
elif buff_id == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
text = f"{buff_value:.1f}%"
tooltip = "AB/MWD module speed increase"
elif buff_id == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
text = f"{buff_value:.1f}%"
tooltip = "Mining/Survey module range bonus"
elif buff_id == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
text = f"{buff_value:+.1f}%"
tooltip = "Mining Modules: Duration & Capacitor-use bonus"
elif buff_id == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
text = f"{buff_value:+.1f}%"
tooltip = "Mining crystal volatility bonus"
textSections = []
tooltipSections = []
buffMap = {}
for seq in (1, 2, 3, 4):
buffId = stuff.getModifiedChargeAttr(f'warfareBuff{seq}ID')
if not buffId:
continue
buffValue = stuff.getModifiedItemAttr(f'warfareBuff{seq}Value')
buffMap[buffId] = buffValue
if buffId == 10: # Shield Burst: Shield Harmonizing: Shield Resistance
# minus buff value because ingame shows positive value
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield resistance")
elif buffId == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield RR duration & capacictor use")
elif buffId == 12: # Shield Burst: Shield Extension: Shield HP
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("shield HP")
elif buffId == 13: # Armor Burst: Armor Energizing: Armor Resistance
# minus buff value because ingame shows positive value
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor resistance")
elif buffId == 14: # Armor Burst: Rapid Repair: Repair Duration/Capacitor
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor RR duration & capacitor use")
elif buffId == 15: # Armor Burst: Armor Reinforcement: Armor HP
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("armor HP")
elif buffId == 16: # Information Burst: Sensor Optimization: Scan Resolution
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("scan resolution")
elif buffId == 26: # Information Burst: Sensor Optimization: Targeting Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("targeting range")
elif buffId == 17: # Information Burst: Electronic Superiority: EWAR Range and Strength
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("electronic warfare modules range & strength")
elif buffId == 18: # Information Burst: Electronic Hardening: Sensor Strength
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("sensor strength")
elif buffId == 19: # Information Burst: Electronic Hardening: RSD/RWD Resistance
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("sensor dampener & weapon disruption resistance")
elif buffId == 20: # Skirmish Burst: Evasive Maneuvers: Signature Radius
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("signature radius")
elif buffId == 60: # Skirmish Burst: Evasive Maneuvers: Agility
# minus the buff value because we want Agility as shown ingame, not inertia modifier
textSections.append(f"{formatAmount(-buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("agility")
elif buffId == 21: # Skirmish Burst: Interdiction Maneuvers: Tackle Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("warp disruption & stasis web range")
elif buffId == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("AB/MWD speed increase")
elif buffId == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining/survey module range")
elif buffId == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining module duration & capacitor use")
elif buffId == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility
textSections.append(f"{formatAmount(buffValue, 3, 0, 3, forceSign=True)}%")
tooltipSections.append("mining crystal volatility")
if not textSections:
return '', None
text = ' | '.join(textSections)
tooltip = '{} bonus'.format(' | '.join(tooltipSections))
if tooltip:
tooltip = tooltip[0].capitalize() + tooltip[1:]
return text, tooltip
elif stuff.charge is not None:
chargeGroup = stuff.charge.group.name
if chargeGroup.endswith("Rocket") or chargeGroup.endswith("Missile") or chargeGroup.endswith("Torpedo"):
@@ -794,7 +788,7 @@ class Miscellanea(ViewColumn):
formatAmount(aoeVelocity, 3, 0, 3), "m/s")
tooltip = "Explosion radius and explosion velocity"
return text, tooltip
elif chargeGroup in ("Bomb", "Structure Guided Bomb"):
elif chargeGroup in ("Bomb", "Guided Bomb"):
cloudSize = stuff.getModifiedChargeAttr("aoeCloudSize")
if not cloudSize:
return "", None

View File

@@ -71,7 +71,7 @@ class CalcReplaceLocalModuleCommand(wx.Command):
# Remove if there was no module
if self.oldModInfo is None:
from .localRemove import CalcRemoveLocalModulesCommand
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.position], recalc=False)
cmd = CalcRemoveLocalModulesCommand(fitID=self.fitID, positions=[self.position], recalc=self.recalc)
if not cmd.Do():
return False
restoreCheckedStates(fit, self.savedStateCheckChanges)

View File

@@ -16,6 +16,7 @@ class GuiChangeBoosterMetaCommand(wx.Command):
self.fitID = fitID
self.position = position
self.newItemID = newItemID
self.newPosition = None
def Do(self):
sFit = Fit.getInstance()
@@ -31,15 +32,24 @@ class GuiChangeBoosterMetaCommand(wx.Command):
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
self.newPosition = cmd.newPosition
newBooster = fit.boosters[self.newPosition]
mainFrame = gui.mainFrame.MainFrame.getInstance()
wx.PostEvent(mainFrame, GE.FitChanged(fitIDs=(self.fitID,)))
wx.PostEvent(mainFrame, GE.ItemChangedInplace(old=booster, new=newBooster))
return success
def Undo(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
oldBooster = fit.boosters[self.newPosition]
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
newBooster = fit.boosters[self.position]
mainFrame = gui.mainFrame.MainFrame.getInstance()
wx.PostEvent(mainFrame, GE.FitChanged(fitIDs=(self.fitID,)))
wx.PostEvent(mainFrame, GE.ItemChangedInplace(old=oldBooster, new=newBooster))
return success

View File

@@ -16,6 +16,7 @@ class GuiChangeImplantMetaCommand(wx.Command):
self.fitID = fitID
self.position = position
self.newItemID = newItemID
self.newPosition = None
def Do(self):
sFit = Fit.getInstance()
@@ -31,15 +32,25 @@ class GuiChangeImplantMetaCommand(wx.Command):
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
self.newPosition = cmd.newPosition
newImplant = fit.implants[self.newPosition]
mainFrame = gui.mainFrame.MainFrame.getInstance()
wx.PostEvent(mainFrame, GE.FitChanged(fitIDs=(self.fitID,)))
wx.PostEvent(mainFrame, GE.ItemChangedInplace(old=implant, new=newImplant))
return success
def Undo(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
oldImplant = fit.implants[self.newPosition]
success = self.internalHistory.undoAll()
eos.db.flush()
sFit = Fit.getInstance()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), GE.FitChanged(fitIDs=(self.fitID,)))
newImplant = fit.implants[self.position]
mainFrame = gui.mainFrame.MainFrame.getInstance()
wx.PostEvent(mainFrame, GE.FitChanged(fitIDs=(self.fitID,)))
wx.PostEvent(mainFrame, GE.ItemChangedInplace(old=oldImplant, new=newImplant))
return success

View File

@@ -22,6 +22,7 @@ class GuiChangeLocalModuleMetasCommand(wx.Command):
def Do(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
oldModMap = self._getPositionMap(fit)
results = []
self.replacedItemIDs = set()
lastSuccessfulCmd = None
@@ -49,6 +50,7 @@ class GuiChangeLocalModuleMetasCommand(wx.Command):
sFit.recalc(self.fitID)
self.savedRemovedDummies = sFit.fill(self.fitID)
eos.db.commit()
newModMap = self._getPositionMap(fit)
events = []
if success and self.replacedItemIDs:
events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.replacedItemIDs))
@@ -56,6 +58,12 @@ class GuiChangeLocalModuleMetasCommand(wx.Command):
events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.newItemID))
if not events:
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
if success:
for position in self.positions:
oldMod = oldModMap.get(position)
newMod = newModMap.get(position)
if oldMod is not newMod:
events.append(GE.ItemChangedInplace(old=oldMod, new=newMod))
for event in events:
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
return success
@@ -63,12 +71,16 @@ class GuiChangeLocalModuleMetasCommand(wx.Command):
def Undo(self):
sFit = Fit.getInstance()
fit = sFit.getFit(self.fitID)
oldModMap = self._getPositionMap(fit)
for position in self.positions:
oldModMap[position] = fit.modules[position]
restoreRemovedDummies(fit, self.savedRemovedDummies)
success = self.internalHistory.undoAll()
eos.db.flush()
sFit.recalc(self.fitID)
sFit.fill(self.fitID)
eos.db.commit()
newModMap = self._getPositionMap(fit)
events = []
if success:
events.append(GE.FitChanged(fitIDs=(self.fitID,), action='moddel', typeID=self.newItemID))
@@ -76,6 +88,18 @@ class GuiChangeLocalModuleMetasCommand(wx.Command):
events.append(GE.FitChanged(fitIDs=(self.fitID,), action='modadd', typeID=self.replacedItemIDs))
if not events:
events.append(GE.FitChanged(fitIDs=(self.fitID,)))
if success:
for position in self.positions:
oldMod = oldModMap.get(position)
newMod = newModMap.get(position)
if oldMod is not newMod:
events.append(GE.ItemChangedInplace(fitID=self.fitID, old=oldMod, new=newMod))
for event in events:
wx.PostEvent(gui.mainFrame.MainFrame.getInstance(), event)
return success
def _getPositionMap(self, fit):
positionMap = {}
for position in self.positions:
positionMap[position] = fit.modules[position]
return positionMap

View File

@@ -57,41 +57,42 @@ class GuiLocalModuleToCargoCommand(wx.Command):
commands.append(cmdReplace)
# Submit batch now because we need to have updated info on fit to keep going
success = self.internalHistory.submitBatch(*commands)
newMod = fit.modules[self.srcModPosition]
# Process charge changes if module is moved to proper slot
if newMod.slot == srcModSlot:
# If we had to unload charge, add it to cargo
if cmdReplace.unloadedCharge and srcModChargeItemID is not None:
cmdAddCargoCharge = CalcAddCargoCommand(
fitID=self.fitID,
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=srcModChargeAmount))
success = self.internalHistory.submit(cmdAddCargoCharge)
# If we did not unload charge and there still was a charge, see if amount differs and process it
elif not cmdReplace.unloadedCharge and srcModChargeItemID is not None:
# How many extra charges do we need to take from cargo
extraChargeAmount = newMod.numCharges - srcModChargeAmount
if extraChargeAmount > 0:
cmdRemoveCargoExtraCharge = CalcRemoveCargoCommand(
if success:
newMod = fit.modules[self.srcModPosition]
# Process charge changes if module is moved to proper slot
if newMod.slot == srcModSlot:
# If we had to unload charge, add it to cargo
if cmdReplace.unloadedCharge and srcModChargeItemID is not None:
cmdAddCargoCharge = CalcAddCargoCommand(
fitID=self.fitID,
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=extraChargeAmount))
# Do not check if operation was successful or not, we're okay if we have no such
# charges in cargo
self.internalHistory.submit(cmdRemoveCargoExtraCharge)
elif extraChargeAmount < 0:
cmdAddCargoExtraCharge = CalcAddCargoCommand(
fitID=self.fitID,
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=abs(extraChargeAmount)))
success = self.internalHistory.submit(cmdAddCargoExtraCharge)
if success:
# Store info to properly send events later
self.removedModItemID = srcModItemID
self.addedModItemID = self.dstCargoItemID
# If drag happened to module which cannot be fit into current slot - consider it as failure
else:
success = False
# And in case of any failures, cancel everything to try to do move instead
if not success:
self.internalHistory.undoAll()
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=srcModChargeAmount))
success = self.internalHistory.submit(cmdAddCargoCharge)
# If we did not unload charge and there still was a charge, see if amount differs and process it
elif not cmdReplace.unloadedCharge and srcModChargeItemID is not None:
# How many extra charges do we need to take from cargo
extraChargeAmount = newMod.numCharges - srcModChargeAmount
if extraChargeAmount > 0:
cmdRemoveCargoExtraCharge = CalcRemoveCargoCommand(
fitID=self.fitID,
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=extraChargeAmount))
# Do not check if operation was successful or not, we're okay if we have no such
# charges in cargo
self.internalHistory.submit(cmdRemoveCargoExtraCharge)
elif extraChargeAmount < 0:
cmdAddCargoExtraCharge = CalcAddCargoCommand(
fitID=self.fitID,
cargoInfo=CargoInfo(itemID=srcModChargeItemID, amount=abs(extraChargeAmount)))
success = self.internalHistory.submit(cmdAddCargoExtraCharge)
if success:
# Store info to properly send events later
self.removedModItemID = srcModItemID
self.addedModItemID = self.dstCargoItemID
# If drag happened to module which cannot be fit into current slot - consider it as failure
else:
success = False
# And in case of any failures, cancel everything to try to do move instead
if not success:
self.internalHistory.undoAll()
# Just dump module and its charges into cargo when copying or moving to cargo
if not success:
commands = []

View File

@@ -11,6 +11,9 @@ GraphOptionChanged, GRAPH_OPTION_CHANGED = wx.lib.newevent.NewEvent()
TargetProfileRenamed, TARGET_PROFILE_RENAMED = wx.lib.newevent.NewEvent()
TargetProfileChanged, TARGET_PROFILE_CHANGED = wx.lib.newevent.NewEvent()
TargetProfileRemoved, TARGET_PROFILE_REMOVED = wx.lib.newevent.NewEvent()
# For events when item is actually replaced under the hood,
# but from user's perspective it's supposed to change/mutate
ItemChangedInplace, ITEM_CHANGED_INPLACE = wx.lib.newevent.NewEvent()
EffectiveHpToggled, EFFECTIVE_HP_TOGGLED = wx.lib.newevent.NewEvent()

View File

@@ -216,3 +216,4 @@ class ItemStatsContainer(wx.Panel):
mutaPanel = getattr(self, 'mutator', None)
if mutaPanel is not None:
mutaPanel.OnWindowClose()
self.params.OnWindowClose()

BIN
imgs/icons/10065@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

BIN
imgs/icons/10065@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
imgs/icons/24968@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

BIN
imgs/icons/24968@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24969@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

BIN
imgs/icons/24969@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24970@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

BIN
imgs/icons/24970@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24971@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

BIN
imgs/icons/24971@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24972@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

BIN
imgs/icons/24972@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/icons/24973@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

BIN
imgs/icons/24973@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24974@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
imgs/icons/24974@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24975@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

BIN
imgs/icons/24975@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24976@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

BIN
imgs/icons/24976@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24977@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

BIN
imgs/icons/24977@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24978@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

BIN
imgs/icons/24978@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24979@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

BIN
imgs/icons/24979@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24980@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

BIN
imgs/icons/24980@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24981@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

BIN
imgs/icons/24981@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24982@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 B

BIN
imgs/icons/24982@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24983@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

BIN
imgs/icons/24983@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24984@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

BIN
imgs/icons/24984@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24985@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 873 B

BIN
imgs/icons/24985@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
imgs/icons/24986@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

BIN
imgs/icons/24986@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/24987@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

BIN
imgs/icons/24987@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/24988@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

BIN
imgs/icons/24988@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
imgs/icons/24989@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

BIN
imgs/icons/24989@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/24990@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

BIN
imgs/icons/24990@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/24991@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 938 B

BIN
imgs/icons/24991@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
imgs/icons/24992@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

BIN
imgs/icons/24992@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24993@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

BIN
imgs/icons/24993@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24994@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

BIN
imgs/icons/24994@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24995@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

BIN
imgs/icons/24995@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24996@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

BIN
imgs/icons/24996@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
imgs/icons/24997@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

BIN
imgs/icons/24997@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/24998@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

BIN
imgs/icons/24998@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/24999@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

BIN
imgs/icons/24999@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/25000@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

BIN
imgs/icons/25000@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/25001@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

BIN
imgs/icons/25001@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/25002@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

BIN
imgs/icons/25002@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/25003@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

BIN
imgs/icons/25003@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
imgs/icons/25021@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

BIN
imgs/icons/25021@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
imgs/icons/25022@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

BIN
imgs/icons/25022@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
imgs/icons/25023@1x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Some files were not shown because too many files have changed in this diff Show More