Merge branch 'master' into attrGroup

# Conflicts:
#	eos/gamedata.py
#	eve.db
#	gui/builtinItemStatsViews/itemAttributes.py
This commit is contained in:
blitzmann
2018-12-01 23:15:35 -05:00
188 changed files with 3787 additions and 362 deletions

View File

@@ -24,10 +24,10 @@ saveInRoot = False
# Version data
version = "2.5.1"
version = "2.6.1"
tag = "Stable"
expansionName = "YC120.10"
expansionVersion = "1.0"
expansionName = "Onslaught"
expansionVersion = "1.5"
evemonMinVersion = "4081"
minItemSearchLength = 3

View File

@@ -237,20 +237,23 @@ class HandledProjectedModList(HandledList):
return
proj.projected = True
isSystemEffect = proj.item.group.name == "Effect Beacon"
if isSystemEffect:
if proj.isExclusiveSystemEffect:
self.makeRoom(proj)
HandledList.append(self, proj)
# Remove non-projectable modules
if not proj.item.isType("projected") and not isSystemEffect:
if not proj.item.isType("projected") and not proj.isExclusiveSystemEffect:
self.remove(proj)
@property
def currentSystemEffect(self):
return next((m for m in self if m.isExclusiveSystemEffect), None)
def makeRoom(self, proj):
# remove other system effects - only 1 per fit plz
oldEffect = next((m for m in self if m.item.group.name == "Effect Beacon"), None)
oldEffect = self.currentSystemEffect
if oldEffect:
pyfalog.info("System effect occupied with {0}, replacing with {1}", oldEffect.item.name, proj.item.name)

View File

@@ -1,7 +1,7 @@
# boosterArmorHpPenalty
#
# Used by:
# Implants named like: Booster (12 of 33)
# Implants named like: Booster (12 of 35)
type = "boosterSideEffect"
# User-friendly name for the side effect

View File

@@ -1,7 +1,7 @@
# boosterShieldCapacityPenalty
#
# Used by:
# Implants from group: Booster (12 of 65)
# Implants from group: Booster (12 of 69)
type = "boosterSideEffect"
# User-friendly name for the side effect

View File

@@ -2,7 +2,7 @@
#
# Used by:
# Ships from group: Carrier (4 of 4)
# Ships from group: Combat Battlecruiser (13 of 13)
# Ships from group: Combat Battlecruiser (14 of 14)
# Ships from group: Command Ship (8 of 8)
# Ships from group: Force Auxiliary (6 of 6)
# Ships from group: Supercarrier (6 of 6)

View File

@@ -3,7 +3,7 @@
# Used by:
# Modules from group: Frequency Mining Laser (3 of 3)
type = "passive"
runTime = "late"
def handler(fit, module, context):
module.preAssignItemAttr("specialtyMiningAmount", module.getModifiedItemAttr("miningAmount"))

View File

@@ -1,7 +1,7 @@
# disintegratorWeaponDamageMultiply
#
# Used by:
# Modules from group: Entropic Radiation Sink (3 of 3)
# Modules from group: Entropic Radiation Sink (4 of 4)
type = "passive"

View File

@@ -1,7 +1,7 @@
# disintegratorWeaponSpeedMultiply
#
# Used by:
# Modules from group: Entropic Radiation Sink (3 of 3)
# Modules from group: Entropic Radiation Sink (4 of 4)
type = "passive"

View File

@@ -2,6 +2,8 @@
#
# Used by:
# Modules named like: Drone Speed Augmentor (6 of 8)
# Implant: Overmind 'Goliath' Drone Tuner T25-10S
# Implant: Overmind 'Hawkmoth' Drone Tuner S10-25T
type = "passive"

View File

@@ -1,3 +1,7 @@
# eliteBonusCovertOps3PCTdamagePerCycle
#
# Used by:
# Ship: Hydra
type = "passive"

View File

@@ -1,10 +0,0 @@
# eliteBonusMaxDmgMultiBonusAdd
#
# Used by:
# Ship: Hydra
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"), "damageMultiplierBonusMax",
src.getModifiedItemAttr("eliteBonusCovertOps3"), skill="Covert Ops")

View File

@@ -1,10 +0,0 @@
# eliteBonusReconMaxDmgMultiMaxHPT
#
# Used by:
# Ship: Tiamat
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemIncrease(lambda mod: mod.item.requiresSkill("Medium Precursor Weapon"), "damageMultiplierBonusMax",
src.getModifiedItemAttr("eliteBonusReconShip3"), skill="Recon Ships")

View File

@@ -1,3 +1,7 @@
# eliteBonusReconShip3PCTdamagePerCycle
#
# Used by:
# Ship: Tiamat
type = "passive"

View File

@@ -1,3 +1,7 @@
# emergencyHullEnergizer
#
# Used by:
# Variations of module: Capital Emergency Hull Energizer I (5 of 5)
type = "active"
runtime = "late"

View File

@@ -1,7 +1,7 @@
# energyWeaponDamageMultiply
#
# Used by:
# Modules from group: Heat Sink (18 of 18)
# Modules from group: Heat Sink (19 of 19)
type = "passive"

View File

@@ -1,7 +1,7 @@
# energyWeaponSpeedMultiply
#
# Used by:
# Modules from group: Heat Sink (18 of 18)
# Modules from group: Heat Sink (19 of 19)
type = "passive"

View File

@@ -1,7 +1,7 @@
# hybridWeaponDamageMultiply
#
# Used by:
# Modules from group: Magnetic Field Stabilizer (14 of 14)
# Modules from group: Magnetic Field Stabilizer (15 of 15)
type = "passive"

View File

@@ -1,7 +1,7 @@
# hybridWeaponSpeedMultiply
#
# Used by:
# Modules from group: Magnetic Field Stabilizer (14 of 14)
# Modules from group: Magnetic Field Stabilizer (15 of 15)
type = "passive"

View File

@@ -1,3 +1,7 @@
# massEntanglerEffect5
#
# Used by:
# Module: Zero-Point Mass Entangler
type = "active"

View File

@@ -4,6 +4,7 @@
# Modules from group: Frequency Mining Laser (3 of 3)
# Modules from group: Mining Laser (15 of 15)
# Modules from group: Strip Miner (5 of 5)
# Module: Citizen Miner
type = 'active'

View File

@@ -1,7 +1,7 @@
# missileDMGBonus
#
# Used by:
# Modules from group: Ballistic Control system (20 of 20)
# Modules from group: Ballistic Control system (21 of 21)
type = "passive"

View File

@@ -1,7 +1,7 @@
# missileLauncherSpeedMultiplier
#
# Used by:
# Modules from group: Ballistic Control system (20 of 20)
# Modules from group: Ballistic Control system (21 of 21)
type = "passive"

View File

@@ -3,6 +3,7 @@
# Used by:
# Modules from group: Drone Damage Modules (11 of 11)
# Modules named like: C3 'Hivaa Saitsuo' Ballistic Control System (2 of 2)
# Module: Abyssal Ballistic Control System
type = "passive"

View File

@@ -7,6 +7,7 @@
# 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)

View File

@@ -1,7 +1,7 @@
# projectileWeaponDamageMultiply
#
# Used by:
# Modules from group: Gyrostabilizer (13 of 13)
# Modules from group: Gyrostabilizer (14 of 14)
type = "passive"

View File

@@ -1,7 +1,7 @@
# projectileWeaponSpeedMultiply
#
# Used by:
# Modules from group: Gyrostabilizer (13 of 13)
# Modules from group: Gyrostabilizer (14 of 14)
type = "passive"

View File

@@ -1,3 +1,11 @@
# roleBonusWarpSpeed
#
# Used by:
# Ship: Cynabal
# Ship: Dramiel
# Ship: Leopard
# Ship: Machariel
# Ship: Victorieux Luxury Yacht
type = "passive"

View File

@@ -3,6 +3,7 @@
# Used by:
# Structure Modules from group: Structure Citadel Service Module (2 of 2)
# Structure Modules from group: Structure Engineering Service Module (6 of 6)
# Structure Modules from group: Structure Navigation Service Module (3 of 3)
# Structure Modules from group: Structure Resource Processing Service Module (4 of 4)
# Structure Module: Standup Moon Drill I
type = "passive"

View File

@@ -0,0 +1,10 @@
# shipArmorEMResistancePBC2
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.ship.boostItemAttr("armorEmDamageResonance", ship.getModifiedItemAttr("shipBonusPBC2"),
skill="Precursor Battlecruiser")

View File

@@ -0,0 +1,10 @@
# shipArmorExplosiveResistancePBC2
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.ship.boostItemAttr("armorExplosiveDamageResonance", ship.getModifiedItemAttr("shipBonusPBC2"),
skill="Precursor Battlecruiser")

View File

@@ -0,0 +1,10 @@
# shipArmorKineticResistancePBC2
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.ship.boostItemAttr("armorKineticDamageResonance", ship.getModifiedItemAttr("shipBonusPBC2"),
skill="Precursor Battlecruiser")

View File

@@ -0,0 +1,10 @@
# shipArmorThermalResistancePBC2
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.ship.boostItemAttr("armorThermalDamageResonance", ship.getModifiedItemAttr("shipBonusPBC2"),
skill="Precursor Battlecruiser")

View File

@@ -1,8 +1,7 @@
# shipBonusDreadnoughtC2ShieldResists
#
# Used by:
# Ship: Caiman
# Ship: Phoenix
# Variations of ship: Phoenix (2 of 2)
type = "passive"

View File

@@ -6,5 +6,5 @@ type = "passive"
def handler(fit, ship, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == "Mining Drone",
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill("Drones"),
"miningAmount", ship.getModifiedItemAttr("shipBonusAC2"), skill="Amarr Cruiser")

View File

@@ -7,5 +7,5 @@ type = "passive"
def handler(fit, ship, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == "Mining Drone",
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill("Mining Drone Operation"),
"miningAmount", ship.getModifiedItemAttr("shipBonusGC2"), skill="Gallente Cruiser")

View File

@@ -1,10 +0,0 @@
# shipBonusForceAuxiliaryA3CapCapacity
#
# Used by:
# Ship: Apostle
type = "passive"
def handler(fit, src, context):
fit.ship.boostItemAttr("capacitorCapacity", src.getModifiedItemAttr("shipBonusForceAuxiliaryA3"),
skill="Amarr Carrier")

View File

@@ -1,8 +1,7 @@
# shipBonusForceAuxiliaryC2ShieldResists
#
# Used by:
# Ship: Loggerhead
# Ship: Minokawa
# Variations of ship: Minokawa (2 of 2)
type = "passive"

View File

@@ -1,10 +0,0 @@
# shipBonusForceAuxiliaryC3CapCapacity
#
# Used by:
# Ship: Minokawa
type = "passive"
def handler(fit, src, context):
fit.ship.boostItemAttr("capacitorCapacity", src.getModifiedItemAttr("shipBonusForceAuxiliaryC3"),
skill="Caldari Carrier")

View File

@@ -1,10 +0,0 @@
# shipBonusForceAuxiliaryG3CapBoosterStrength
#
# Used by:
# Ship: Ninazu
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.group.name == "Capacitor Booster Charge", "capacitorBonus",
src.getModifiedItemAttr("shipBonusForceAuxiliaryG3"), skill="Gallente Carrier")

View File

@@ -1,10 +0,0 @@
# shipBonusForceAuxiliaryM3CapBoosterStrength
#
# Used by:
# Ship: Lif
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.group.name == "Capacitor Booster Charge", "capacitorBonus",
src.getModifiedItemAttr("shipBonusForceAuxiliaryM3"), skill="Minmatar Carrier")

View File

@@ -9,5 +9,5 @@ type = "passive"
def handler(fit, container, context):
fit.drones.filteredItemBoost(lambda drone: drone.item.group.name == "Mining Drone",
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill("Mining Drone Operation"),
"miningAmount", container.getModifiedItemAttr("rookieDroneBonus"))

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Drekavac
# Ship: Hydra
# Ship: Kikimora
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak

View File

@@ -0,0 +1,11 @@
# shipBonusPBC1DisintegratorDamage
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Medium Precursor Weapon"),
"damageMultiplier", ship.getModifiedItemAttr("shipBonusPBC1"),
skill="Precursor Battlecruiser")

View File

@@ -0,0 +1,11 @@
# shipBonusPD1DisintegratorDamage
#
# Used by:
# Ship: Kikimora
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"),
"damageMultiplier", ship.getModifiedItemAttr("shipBonusPD1"),
skill="Precursor Destroyer")

View File

@@ -0,0 +1,11 @@
# shipBonusPD2DisintegratorMaxRange
#
# Used by:
# Ship: Kikimora
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"),
"maxRange", ship.getModifiedItemAttr("shipBonusPD2"),
skill="Precursor Destroyer")

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Drekavac
# Ship: Hydra
# Ship: Kikimora
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Drekavac
# Ship: Hydra
# Ship: Kikimora
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak

View File

@@ -1,8 +1,7 @@
# shipBonusRole3XLTorpdeoVelocityBonus
#
# Used by:
# Ship: Komodo
# Ship: Leviathan
# Variations of ship: Leviathan (2 of 2)
type = "passive"

View File

@@ -2,7 +2,9 @@
#
# Used by:
# Ship: Damavik
# Ship: Drekavac
# Ship: Hydra
# Ship: Kikimora
# Ship: Leshak
# Ship: Tiamat
# Ship: Vedmak

View File

@@ -1,8 +1,7 @@
# shipBonusTitanA3WarpStrength
#
# Used by:
# Ship: Avatar
# Ship: Molok
# Variations of ship: Avatar (2 of 2)
type = "passive"

View File

@@ -1,8 +1,7 @@
# shipBonusTitanC2ROFBonus
#
# Used by:
# Ship: Komodo
# Ship: Leviathan
# Variations of ship: Leviathan (2 of 2)
type = "passive"

View File

@@ -1,8 +1,7 @@
# shipBonusTitanC3WarpStrength
#
# Used by:
# Ship: Komodo
# Ship: Leviathan
# Variations of ship: Leviathan (2 of 2)
type = "passive"

View File

@@ -0,0 +1,10 @@
# shipRoleDisintegratorMaxRangeCBC
#
# Used by:
# Ship: Drekavac
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Medium Precursor Weapon"),
"maxRange", ship.getModifiedItemAttr("roleBonusCBC"))

View File

@@ -1,7 +1,7 @@
# skillBonusDroneDurability
#
# Used by:
# Implants from group: Cyber Drones (2 of 2)
# Implants from group: Cyber Drones (4 of 4)
# Skill: Drone Durability
type = "passive"

View File

@@ -1,7 +1,8 @@
# skillBonusDroneInterfacing
#
# Used by:
# Implants from group: Cyber Drones (2 of 2)
# Implant: CreoDron 'Bumblebee' Drone Tuner T10-5D
# Implant: CreoDron 'Yellowjacket' Drone Tuner D5-10T
# Skill: Drone Interfacing
type = "passive"

View File

@@ -0,0 +1,10 @@
# smallDisintegratorMaxRangeBonus
#
# Used by:
# Ship: Kikimora
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Precursor Weapon"),
"maxRange", ship.getModifiedItemAttr("maxRangeBonus"))

View File

@@ -1,7 +1,8 @@
# structureAoERoFRoleBonus
#
# Used by:
# Items from category: Structure (11 of 14)
# Items from category: Structure (11 of 17)
# Structures from group: Citadel (8 of 9)
type = "passive"

View File

@@ -1,4 +1,4 @@
# structureArmorHPMultiply
# structureArmorHPBonus
#
# Used by:
# Structure Modules from group: Structure Armor Reinforcer (2 of 2)
@@ -7,4 +7,4 @@ runTime = "early"
def handler(fit, src, context):
fit.ship.multiplyItemAttr("hiddenArmorHPMultiplier", src.getModifiedItemAttr("armorHPMultiplier"))
fit.ship.boostItemAttr("hiddenArmorHPMultiplier", src.getModifiedItemAttr("armorHpBonus"), stackingPenalties=True)

View File

@@ -3,14 +3,20 @@
# Used by:
# Structure Modules from group: Structure Energy Neutralizer (5 of 5)
from eos.saveddata.module import State
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
def handler(fit, container, context):
def handler(fit, src, context, **kwargs):
amount = 0
if "projected" in context:
if (hasattr(container, "state") and container.state >= State.ACTIVE) or hasattr(container, "amountActive"):
amount = container.getModifiedItemAttr("energyNeutralizerAmount")
time = container.getModifiedItemAttr("duration")
fit.addDrain(container, time, amount, 0)
if (hasattr(src, "state") and src.state >= State.ACTIVE) or hasattr(src, "amountActive"):
amount = src.getModifiedItemAttr("energyNeutralizerAmount")
if 'effect' in kwargs:
amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])
time = src.getModifiedItemAttr("duration")
fit.addDrain(src, time, amount, 0)

View File

@@ -1,7 +1,7 @@
# structureFullPowerStateHitpointModifier
#
# Used by:
# Items from category: Structure (14 of 14)
# Items from category: Structure (17 of 17)
type = "passive"

View File

@@ -1,7 +1,7 @@
# structureHiddenArmorHPMultiplier
#
# Used by:
# Items from category: Structure (14 of 14)
# Items from category: Structure (17 of 17)
type = "passive"

View File

@@ -1,7 +1,7 @@
# structureHiddenMissileDamageMultiplier
#
# Used by:
# Items from category: Structure (14 of 14)
# Items from category: Structure (14 of 17)
type = "passive"

View File

@@ -1,7 +1,7 @@
# targetAttack
#
# Used by:
# Drones from group: Combat Drone (74 of 74)
# Drones from group: Combat Drone (75 of 75)
# Modules from group: Energy Weapon (212 of 214)
type = 'active'

View File

@@ -1,4 +1,4 @@
# warpScramble
# warpDisrupt
#
# Used by:
# Modules named like: Warp Disruptor (28 of 28)

View File

@@ -4,15 +4,15 @@
# Celestial: darkness_weather_1
# Celestial: darkness_weather_2
# Celestial: darkness_weather_3
# Celestial: pvp_weather_1
runTime = "early"
type = ("projected", "passive", "gang")
def handler(fit, beacon, context, **kwargs):
for x in range(1, 3):
for x in range(1, 5):
if beacon.getModifiedItemAttr("warfareBuff{}ID".format(x)):
value = beacon.getModifiedItemAttr("warfareBuff{}Value".format(x))
id = beacon.getModifiedItemAttr("warfareBuff{}ID".format(x))
if id:
fit.addCommandBonus(id, value, beacon, kwargs['effect'], 'early')

View File

@@ -576,51 +576,67 @@ class Unit(EqBase):
""" This is a mapping of various tweaks that we have to do between the internal representation of an attribute
value and the display (for example, 'Millisecond' units have the display name of 's', so we have to convert value
from ms to s) """
# Each entry contains:
# Function to convert value to display value
# Function to convert value to display format (which sometimes can be a string)
# Function which controls unit name used with attribute
# Function to convert display value to value
return {
"Inverse Absolute Percent": (
lambda v: (1 - v) * 100,
lambda d: -1 * (d / 100) + 1,
lambda u: u),
lambda v: (1 - v) * 100,
lambda u: u,
lambda d: -1 * (d / 100) + 1),
"Inversed Modifier Percent": (
lambda v: (1 - v) * 100,
lambda d: -1 * (d / 100) + 1,
lambda u: u),
lambda v: (1 - v) * 100,
lambda u: u,
lambda d: -1 * (d / 100) + 1),
"Modifier Percent": (
lambda v: (v - 1) * 100,
lambda v: ("%+.2f" if ((v - 1) * 100) % 1 else "%+d") % ((v - 1) * 100),
lambda d: (d / 100) + 1,
lambda u: u),
lambda u: u,
lambda d: (d / 100) + 1),
"Volume": (
lambda v: v,
lambda d: d,
lambda u: ""),
lambda v: v,
lambda u: "",
lambda d: d),
"Sizeclass": (
lambda v: self.rigSizes[v],
lambda v: self.rigSizes[v],
lambda d: next(i for i in self.rigSizes.keys() if self.rigSizes[i] == 'Medium'),
lambda u: ""),
"Absolute Percent": (
lambda v: (v * 100),
lambda d: d / 100,
lambda u: u),
lambda v: v * 100,
lambda v: v * 100,
lambda u: u,
lambda d: d / 100),
"Milliseconds": (
lambda v: v / 1000.0,
lambda d: d * 1000.0,
lambda u: u),
lambda v: v / 1000,
lambda v: v / 1000,
lambda u: u,
lambda d: d * 1000),
"Boolean": (
lambda v: "Yes" if v == 1 else "No",
lambda d: 1.0 if d == "Yes" else 0.0,
lambda u: ""),
lambda v: True if v else False,
lambda v: "Yes" if v else "No",
lambda u: "",
lambda d: 1.0 if d == "Yes" else 0.0),
"typeID": (
self.itemIDCallback,
None, # we could probably convert these back if we really tried hard enough
lambda u: ""),
self.itemIDCallback,
lambda u: "",
None), # we could probably convert these back if we really tried hard enough
"groupID": (
self.groupIDCallback,
None,
lambda u: ""),
self.groupIDCallback,
lambda u: "",
None),
"attributeID": (
self.attributeIDCallback,
None,
lambda u: ""),
self.attributeIDCallback,
lambda u: "",
None),
}
@staticmethod
@@ -643,25 +659,33 @@ class Unit(EqBase):
attribute = eos.db.getAttributeInfo(v, eager="unit")
return "%s (%d)" % (attribute.name.capitalize(), v)
def TranslateValue(self, value):
def PreformatValue(self, value):
"""Attributes have to be translated certain ways based on their unit (ex: decimals converting to percentages).
This allows us to get an easy representation of how the attribute should be printed """
override = self.translations.get(self.name)
if override is not None:
return override[0](value), override[2](self.displayName)
return override[1](value), override[2](self.displayName)
return value, self.displayName
def SimplifyValue(self, value):
"""Takes the internal representation value and convert it into the display value"""
override = self.translations.get(self.name)
if override is not None:
return override[0](value)
return value
def ComplicateValue(self, value):
"""Takes the display value and turns it back into the internal representation of it"""
override = self.translations.get(self.name)
if override is not None:
return override[1](value)
return override[3](value)
return value
class Traits(EqBase):
pass

View File

@@ -93,6 +93,8 @@ class FitDpsGraph(Graph):
# this is janky as fuck
for fighter in fit.fighters:
if not fighter.active:
continue
for ability in fighter.abilities:
if ability.dealsDamage and ability.active:
multiplier = self.calculateFighterMissileMultiplier(ability, data)

View File

@@ -692,7 +692,7 @@ class Fit(object):
self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Shield Operation"),
"duration", value, stackingPenalties=True)
# Abysmal Weather Effects
# Abyssal Weather Effects
if warfareBuffID == 90: # Weather_electric_storm_EM_resistance_penalty
for tankType in ("shield", "armor"):

View File

@@ -272,7 +272,17 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def modPosition(self):
if self.owner:
return self.owner.modules.index(self)
return self.owner.modules.index(self) if not self.isProjected else self.owner.projectedModules.index(self)
@property
def isProjected(self):
if self.owner:
return self in self.owner.projectedModules
return None
@property
def isExclusiveSystemEffect(self):
return self.item.group.name in ("Effect Beacon", "Non-Interactable Object", "MassiveEnvironments")
@property
def isCapitalSize(self):

View File

@@ -167,8 +167,6 @@ class AttributeGauge(wx.Window):
def SetValue(self, value, animate=True):
""" Sets the current position of the gauge. """
print("=" * 20, self._percentage)
if self._value == value:
return

View File

@@ -151,7 +151,7 @@ class CargoView(d.Display):
self.original = fit.cargo if fit is not None else None
self.cargo = stuff = fit.cargo if fit is not None else None
if stuff is not None:
stuff.sort(key=lambda cargo: cargo.itemID)
stuff.sort(key=lambda c: (c.item.group.category.name, c.item.group.name, c.item.name))
if event.fitID != self.lastFitId:
self.lastFitId = event.fitID

View File

@@ -23,6 +23,7 @@ import gui.display as d
from gui.builtinMarketBrowser.events import ITEM_SELECTED
import gui.mainFrame
from gui.builtinViewColumns.state import State
from gui.utils.staticHelpers import DragDropHelper
from gui.contextMenu import ContextMenu
import gui.globalEvents as GE
from eos.saveddata.fit import ImplantLocation
@@ -31,6 +32,22 @@ from service.market import Market
import gui.fitCommands as cmd
class ImplantViewDrop(wx.DropTarget):
def __init__(self, dropFn, *args, **kwargs):
super(ImplantViewDrop, self).__init__(*args, **kwargs)
self.dropFn = dropFn
# this is really transferring an EVE itemID
self.dropData = wx.TextDataObject()
self.SetDataObject(self.dropData)
def OnData(self, x, y, t):
if self.GetData():
dragged_data = DragDropHelper.data
data = dragged_data.split(':')
self.dropFn(x, y, data)
return t
class ImplantView(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL)
@@ -77,10 +94,7 @@ class ImplantView(wx.Panel):
def OnRadioSelect(self, event):
fitID = self.mainFrame.getActiveFit()
if fitID is not None:
sFit = Fit.getInstance()
sFit.toggleImplantSource(fitID, ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
self.mainFrame.command.Submit(cmd.GuiChangeImplantLocation(fitID, ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER))
class ImplantDisplay(d.Display):
@@ -102,12 +116,27 @@ class ImplantDisplay(d.Display):
self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem)
self.Bind(wx.EVT_LEFT_DOWN, self.click)
self.Bind(wx.EVT_KEY_UP, self.kbEvent)
self.SetDropTarget(ImplantViewDrop(self.handleListDrag))
if "__WXGTK__" in wx.PlatformInfo:
self.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
else:
self.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
def handleListDrag(self, x, y, data):
"""
Handles dragging of items from various pyfa displays which support it
data is list with two indices:
data[0] is hard-coded str of originating source
data[1] is typeID or index of data we want to manipulate
"""
if data[0] == "market":
if self.mainFrame.command.Submit(cmd.GuiAddImplantCommand(self.mainFrame.getActiveFit(), int(data[1]))):
self.mainFrame.additionsPane.select("Implants")
def kbEvent(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_DELETE or keycode == wx.WXK_NUMPAD_DELETE:

View File

@@ -30,8 +30,9 @@ class ModuleGlobalAmmoPicker(ModuleAmmoPicker):
fit = db_getFit(fitID)
selectedModule = self.modules[0]
source = fit.modules if not selectedModule.isProjected else fit.projectedModules
allModules = []
for mod in fit.modules:
for mod in source:
if mod.itemID is None:
continue
if mod.itemID == selectedModule.itemID:

View File

@@ -15,7 +15,7 @@ class WhProjector(ContextMenu):
# CCP doesn't currently provide a mapping between the general Environment, and the specific environment effect
# (which can be random when going into Abyssal space). This is how we currently define it:
# environment type: specific type name previx
# environment type: specific type name prefix
abyssal_mapping = {
'caustic_toxin_weather': 47862, # Exotic Particle Storm
'darkness_weather': 47863, # Dark Matter Field
@@ -50,13 +50,13 @@ class WhProjector(ContextMenu):
wormhole_item.SetSubMenu(wormhole_menu)
sub.Append(wormhole_item)
effdata = self.getEffectBeacons()
self.buildMenu(effdata, wormhole_menu, rootMenu, msw)
grouped_data, flat_data = self.getEffectBeacons()
self.buildMenu(grouped_data, flat_data, wormhole_menu, rootMenu, msw)
# Incursions
effdata = self.getEffectBeacons(incursions=True)
self.buildMenu(effdata, sub, rootMenu, msw)
grouped_data, flat_data = self.getEffectBeacons(incursions=True)
self.buildMenu(grouped_data, flat_data, sub, rootMenu, msw)
# Abyssal Weather
@@ -65,8 +65,8 @@ class WhProjector(ContextMenu):
abyssal_item.SetSubMenu(abyssal_menu)
sub.Append(abyssal_item)
effdata = self.getAbyssalWeather()
self.buildMenu(effdata, abyssal_menu, rootMenu, msw)
grouped_data, flat_data = self.getAbyssalWeather()
self.buildMenu(grouped_data, flat_data, abyssal_menu, rootMenu, msw)
# Localized Weather
@@ -75,8 +75,8 @@ class WhProjector(ContextMenu):
local_item.SetSubMenu(local_menu)
sub.Append(local_item)
effdata = self.getLocalizedEnvironments()
self.buildMenu(effdata, local_menu, rootMenu, msw)
grouped_data, flat_data = self.getLocalizedEnvironments()
self.buildMenu(grouped_data, flat_data, local_menu, rootMenu, msw)
return sub
@@ -91,33 +91,38 @@ class WhProjector(ContextMenu):
fitID = self.mainFrame.getActiveFit()
self.mainFrame.command.Submit(cmd.GuiAddProjectedCommand(fitID, swObj.ID, 'item'))
def buildMenu(self, data, local_menu, rootMenu, msw):
for swType in sorted(data):
def buildMenu(self, grouped_data, flat_data, local_menu, rootMenu, msw):
def processFlat(data, root, sub):
for swData in sorted(data, key=lambda tpl: tpl[2]):
wxid = ContextMenu.nextID()
swObj, swName, swClass = swData
self.idmap[wxid] = (swObj, swName)
subItem = wx.MenuItem(sub, wxid, swClass)
if msw:
root.Bind(wx.EVT_MENU, self.handleSelection, subItem)
else:
sub.Bind(wx.EVT_MENU, self.handleSelection, subItem)
sub.Append(subItem)
for swType in sorted(grouped_data):
subItem = wx.MenuItem(local_menu, wx.ID_ANY, swType)
grandSub = wx.Menu()
subItem.SetSubMenu(grandSub)
local_menu.Append(subItem)
processFlat(grouped_data[swType], rootMenu, grandSub)
for swData in sorted(data[swType], key=lambda tpl: tpl[2]):
wxid = ContextMenu.nextID()
swObj, swName, swClass = swData
self.idmap[wxid] = (swObj, swName)
grandSubItem = wx.MenuItem(grandSub, wxid, swClass)
if msw:
rootMenu.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem)
else:
grandSub.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem)
grandSub.Append(grandSubItem)
processFlat(flat_data, rootMenu, local_menu)
def getEffectBeacons(self, incursions=False):
"""
Get dictionary with system-wide effects
Get dictionary with wormhole system-wide effects
"""
sMkt = Market.getInstance()
# todo: rework this
# Container for system-wide effects
effects = {}
grouped = {}
# Expressions for matching when detecting effects we're looking for
if incursions:
@@ -158,13 +163,13 @@ class WhProjector(ContextMenu):
groupname = re.sub(garbage, "", groupname)
groupname = re.sub(" {2,}", " ", groupname).strip()
# Add stuff to dictionary
if groupname not in effects:
effects[groupname] = set()
effects[groupname].add((beacon, beaconname, shortname))
if groupname not in grouped:
grouped[groupname] = set()
grouped[groupname].add((beacon, beaconname, shortname))
# Break loop on 1st result
break
return effects
return grouped, ()
def getAbyssalWeather(self):
sMkt = Market.getInstance()
@@ -172,7 +177,8 @@ class WhProjector(ContextMenu):
environments = {x.ID: x for x in sMkt.getGroup("Abyssal Environment").items}
items = chain(sMkt.getGroup("MassiveEnvironments").items, sMkt.getGroup("Non-Interactable Object").items)
effects = {}
grouped = {}
flat = set()
for beacon in items:
if not beacon.isType('projected'):
@@ -183,20 +189,23 @@ class WhProjector(ContextMenu):
if type is None:
continue
if type.name not in effects:
effects[type.name] = set()
if type.name not in grouped:
grouped[type.name] = set()
display_name = "{} {}".format(type.name, beacon.name[-1:])
effects[type.name].add((beacon, display_name, display_name))
grouped[type.name].add((beacon, display_name, display_name))
return effects
# PVP weather
flat.add((sMkt.getItem(49766), 'PvP Weather', 'PvP Weather'))
return grouped, flat
def getLocalizedEnvironments(self):
sMkt = Market.getInstance()
grp = sMkt.getGroup("Abyssal Hazards")
effects = dict()
grouped = dict()
for beacon in grp.items:
if not beacon.isType('projected'):
@@ -206,12 +215,12 @@ class WhProjector(ContextMenu):
name_parts = beacon.name.split(" ")
key = name_parts[1].strip()
if key not in effects:
effects[key] = set()
if key not in grouped:
grouped[key] = set()
effects[key].add((beacon, beacon.name, beacon.name))
grouped[key].add((beacon, beacon.name, beacon.name))
return effects
return grouped, ()
WhProjector.register()

View File

@@ -46,17 +46,17 @@ class AttributeSlider(wx.Panel):
# Slider which abstracts users values from internal values (because the built in slider does not deal with floats
# and the like), based on http://wxpython-users.wxwidgets.narkive.com/ekgBzA7u/anyone-ever-thought-of-a-floating-point-slider
def __init__(self, parent, baseValue, minMod, maxMod, inverse=False, id=-1):
def __init__(self, parent, baseValue, minValue, maxValue, inverse=False, id=-1):
wx.Panel.__init__(self, parent, id=id)
self.parent = parent
self.inverse = inverse
self.base_value = baseValue
self.UserMinValue = minMod
self.UserMaxValue = maxMod
self.UserMinValue = minValue
self.UserMaxValue = maxValue
self.inverse = inverse
# The internal slider basically represents the percentage towards the end of the range. It has to be normalized
# in this way, otherwise when we start off with a base, if the range is skewed to one side, the base value won't
@@ -65,12 +65,12 @@ class AttributeSlider(wx.Panel):
# Additionally, since we want the slider to be accurate to 3 decimal places, we need to blow out the two ends here
# (if we have a slider that needs to land on 66.66% towards the right, it will actually be converted to 66%. Se we need it to support 6,666)
#
# self.SliderMinValue = -100
# self.SliderMaxValue = 100
# self.SliderValue = 0
self.SliderMinValue = -100
self.SliderMaxValue = 100
self.SliderValue = 0
range = [(self.UserMinValue * self.base_value), (self.UserMaxValue * self.base_value)]
range = [self.UserMinValue, self.UserMaxValue]
self.ctrl = wx.SpinCtrlDouble(self, min=min(range), max=max(range))
self.ctrl.SetDigits(3)
@@ -92,19 +92,12 @@ class AttributeSlider(wx.Panel):
evt.Skip()
def SetValue(self, value, post_event=True):
# todo: check this against values that might be 2.5x and whatnot
mod = value / self.base_value
self.ctrl.SetValue(value)
slider_percentage = 0
if mod < 1:
modEnd = self.UserMinValue
slider_percentage = (1 - mod) / (1 - modEnd) * -100
elif mod > 1:
modEnd = self.UserMaxValue
slider_percentage = ((mod - 1) / (modEnd - 1)) * 100
# print(slider_percentage)
if self.inverse:
slider_percentage *= -1
invert_factor = -1 if self.inverse else 1
if value >= self.base_value:
slider_percentage = (value - self.base_value) / (self.UserMaxValue - self.base_value) * 100 * invert_factor
else:
slider_percentage = (value - self.base_value) / (self.base_value - self.UserMinValue) * 100 * invert_factor
self.slider.SetValue(slider_percentage)
if post_event:
wx.PostEvent(self, ValueChanged(self, None, value, None, slider_percentage))

View File

@@ -7,7 +7,7 @@ import wx.lib.agw.hypertreelist
from gui.builtinItemStatsViews.helpers import AutoListCtrl
from gui.bitmap_loader import BitmapLoader
from gui.utils.numberFormatter import formatAmount
from gui.utils.numberFormatter import formatAmount, roundDec
from enum import IntEnum
from gui.builtinItemStatsViews.attributeGrouping import *
@@ -294,14 +294,14 @@ class ItemParams(wx.Panel):
if self.toggleView != 1:
valueUnit = str(value)
elif info and info.unit:
valueUnit = self.FormatValue(*info.unit.TranslateValue(value))
valueUnit = self.FormatValue(*info.unit.PreformatValue(value))
else:
valueUnit = formatAmount(value, 3, 0, 0)
if self.toggleView != 1:
valueUnitDefault = str(valueDefault)
elif info and info.unit:
valueUnitDefault = self.FormatValue(*info.unit.TranslateValue(valueDefault))
valueUnitDefault = self.FormatValue(*info.unit.PreformatValue(valueDefault))
else:
valueUnitDefault = formatAmount(valueDefault, 3, 0, 0)
@@ -314,11 +314,13 @@ class ItemParams(wx.Panel):
# self.paramList.SetItemImage(index, attrIcon, which=wx.TreeItemIcon_Normal)
@staticmethod
def FormatValue(value, unit):
def FormatValue(value, unit, rounding='prec', digits=3):
"""Formats a value / unit combination into a string
@todo: move this to a more central location, since this is also used in the item mutator panel"""
if isinstance(value, (int, float)):
fvalue = formatAmount(value, 3, 0, 0)
if isinstance(value, (int, float)) and rounding == 'prec':
fvalue = formatAmount(value, digits, 0, 0)
elif isinstance(value, (int, float)) and rounding == 'dec':
fvalue = roundDec(value, digits)
else:
fvalue = value
return "%s %s" % (fvalue, unit)

View File

@@ -30,17 +30,9 @@ class ItemMutator(wx.Panel):
self.event_mapping = {}
for m in sorted(stuff.mutators.values(), key=lambda x: x.attribute.displayName):
baseValueFormated = m.attribute.unit.TranslateValue(m.baseValue)[0]
valueFormated = m.attribute.unit.TranslateValue(m.value)[0]
slider = AttributeSlider(self, baseValueFormated, m.minMod, m.maxMod, not m.highIsGood)
slider.SetValue(valueFormated, False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
# create array for the two ranges
min_t = [round(m.minValue, 3), m.minMod, None]
max_t = [round(m.maxValue, 3), m.maxMod, None]
min_t = [m.minValue, m.minMod, None]
max_t = [m.maxValue, m.maxMod, None]
# Then we need to determine if it's better than original, which will be the color
min_t[2] = min_t[1] < 1 if not m.highIsGood else 1 < min_t[1]
@@ -56,14 +48,8 @@ class ItemMutator(wx.Panel):
worse_range = max_t
else:
worse_range = min_t
#
# print("{}: \nHigh is good: {}".format(m.attribute.displayName, m.attribute.highIsGood))
# print("Value {}".format(m.baseValue))
#
# print(min_t)
# print(max_t)
# print(better_range)
# print(worse_range)
headingSizer = wx.BoxSizer(wx.HORIZONTAL)
font = parent.GetFont()
font.SetWeight(wx.BOLD)
@@ -75,19 +61,30 @@ class ItemMutator(wx.Panel):
headingSizer.Add(displayName, 3, wx.ALL | wx.EXPAND, 0)
range_low = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(worse_range[0])))
range_low.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
worst_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(worse_range[0]), rounding='dec')
worst_text = wx.StaticText(self, wx.ID_ANY, worst_val)
worst_text.SetForegroundColour(self.goodColor if worse_range[2] else self.badColor)
range_high = wx.StaticText(self, wx.ID_ANY, ItemParams.FormatValue(*m.attribute.unit.TranslateValue(better_range[0])))
range_high.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
best_val = ItemParams.FormatValue(*m.attribute.unit.PreformatValue(better_range[0]), rounding='dec')
best_text = wx.StaticText(self, wx.ID_ANY, best_val)
best_text.SetForegroundColour(self.goodColor if better_range[2] else self.badColor)
headingSizer.Add(range_low, 0, wx.ALL | wx.EXPAND, 0)
headingSizer.Add(worst_text, 0, wx.ALL | wx.EXPAND, 0)
headingSizer.Add(wx.StaticText(self, wx.ID_ANY, ""), 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 5)
headingSizer.Add(range_high, 0, wx.RIGHT | wx.EXPAND, 10)
headingSizer.Add(best_text, 0, wx.RIGHT | wx.EXPAND, 10)
mainSizer.Add(headingSizer, 0, wx.ALL | wx.EXPAND, 5)
slider = AttributeSlider(parent=self,
baseValue=m.attribute.unit.SimplifyValue(m.baseValue),
minValue=m.attribute.unit.SimplifyValue(min_t[0]),
maxValue=m.attribute.unit.SimplifyValue(max_t[0]),
inverse=better_range is min_t)
slider.SetValue(m.attribute.unit.SimplifyValue(m.value), False)
slider.Bind(EVT_VALUE_CHANGED, self.changeMutatedValue)
self.event_mapping[slider] = m
mainSizer.Add(slider, 0, wx.RIGHT | wx.LEFT | wx.EXPAND, 10)
mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.ALL | wx.EXPAND, 5)
mainSizer.AddStretchSpacer()
@@ -132,7 +129,7 @@ class ItemMutator(wx.Panel):
for slider, m in self.event_mapping.items():
value = sFit.changeMutatedValue(m, m.baseValue)
value = m.attribute.unit.TranslateValue(value)[0]
value = m.attribute.unit.SimplifyValue(value)
slider.SetValue(value)
evt.Skip()
@@ -143,7 +140,7 @@ class ItemMutator(wx.Panel):
for slider, m in self.event_mapping.items():
value = random.uniform(m.minValue, m.maxValue)
value = sFit.changeMutatedValue(m, value)
value = m.attribute.unit.TranslateValue(value)[0]
value = m.attribute.unit.SimplifyValue(value)
slider.SetValue(value)
evt.Skip()

View File

@@ -736,6 +736,7 @@ class _TabsContainer(wx.Panel):
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_SYS_COLOUR_CHANGED, self.OnSysColourChanged)
@@ -776,6 +777,29 @@ class _TabsContainer(wx.Panel):
self.dragged_tab = tab
def OnMiddleUp(self, event):
mposx, mposy = event.GetPosition()
tab = self.FindTabAtPos(mposx, mposy)
if tab is None or not tab.closeable: # if not able to close, return False
return False
index = self.tabs.index(tab)
ev = PageClosing(index)
wx.PostEvent(self.Parent, ev)
if ev.isVetoed():
return False
index = self.GetTabIndex(tab)
self.Parent.DeletePage(index)
wx.PostEvent(self.Parent, PageClosed(index=index))
sel = self.GetSelected()
if sel is not None:
wx.PostEvent(self.Parent, PageChanged(-1, sel))
def OnMotion(self, event):
"""
Determines what happens when the mouse moves. This handles primarily

View File

@@ -131,7 +131,7 @@ class EveFittings(wx.Frame):
return
data = self.fitTree.fittingsTreeCtrl.GetItemData(selection)
sPort = Port.getInstance()
fits = sPort.importFitFromBuffer(data)
import_type, fits = sPort.importFitFromBuffer(data)
self.mainFrame._openAfterImport(fits)
def deleteFitting(self, event):

View File

@@ -31,4 +31,6 @@ from .guiChangeProjectedFitQty import GuiChangeProjectedFitQty
from .guiChangeDroneQty import GuiChangeDroneQty
from .guiChangeProjectedDroneQty import GuiChangeProjectedDroneQty
from .guiToggleDrone import GuiToggleDroneCommand
from .guiFitRename import GuiFitRenameCommand
from .guiFitRename import GuiFitRenameCommand
from .guiChangeImplantLocation import GuiChangeImplantLocation
from .guiImportMutatedModule import GuiImportMutatedModuleCommand

View File

@@ -28,11 +28,12 @@ class FitAddProjectedEnvCommand(wx.Command):
# todo: thing to check for existing environmental effects
self.old_item = fit.projectedModules.makeRoom(module)
module.state = State.ONLINE
fit.projectedModules.append(module)
if module.isExclusiveSystemEffect:
# if this is an exclusive system effect, we need to cache the old one. We make room for the new one here, which returns the old one
self.old_item = fit.projectedModules.makeRoom(module)
fit.projectedModules.append(module)
eos.db.commit()
self.new_index = fit.projectedModules.index(module)
return True

View File

@@ -0,0 +1,25 @@
import wx
import eos.db
from logbook import Logger
pyfalog = Logger(__name__)
class FitChangeImplantLocation(wx.Command):
def __init__(self, fitID, source):
wx.Command.__init__(self, True, "Drone add")
self.fitID = fitID
self.source = source
self.old_source = None
def Do(self):
pyfalog.debug("Toggling implant source for fit ID: {0}", self.fitID)
fit = eos.db.getFit(self.fitID)
self.old_source = fit.implantSource
fit.implantSource = self.source
eos.db.commit()
return True
def Undo(self):
cmd = FitChangeImplantLocation(self.fitID, self.old_source)
return cmd.Do()

View File

@@ -0,0 +1,93 @@
import wx
from eos.saveddata.module import Module, State
import eos.db
from eos.db.gamedata.queries import getDynamicItem
from logbook import Logger
from service.fit import Fit
pyfalog = Logger(__name__)
class FitImportMutatedCommand(wx.Command):
""""
Fitting command that takes info about mutated module, composes it and adds it to a fit
"""
def __init__(self, fitID, baseItem, mutaItem, attrMap):
wx.Command.__init__(self, True)
self.fitID = fitID
self.baseItem = baseItem
self.mutaItem = mutaItem
self.attrMap = attrMap
self.new_position = None
self.change = None
self.replace_cmd = None
def Do(self):
sFit = Fit.getInstance()
fitID = self.fitID
fit = eos.db.getFit(fitID)
if self.baseItem is None:
pyfalog.warning("Unable to build non-mutated module: no base item to build from")
return False
try:
mutaTypeID = self.mutaItem.ID
except AttributeError:
mutaplasmid = None
else:
mutaplasmid = getDynamicItem(mutaTypeID)
# Try to build simple item even though no mutaplasmid found
if mutaplasmid is None:
try:
module = Module(self.baseItem)
except ValueError:
pyfalog.warning("Unable to build non-mutated module: {}", self.baseItem)
return False
# Build mutated module otherwise
else:
try:
module = Module(mutaplasmid.resultingItem, self.baseItem, mutaplasmid)
except ValueError:
pyfalog.warning("Unable to build mutated module: {} {}", self.baseItem, self.mutaItem)
return False
else:
for attrID, mutator in module.mutators.items():
if attrID in self.attrMap:
mutator.value = self.attrMap[attrID]
# this is essentially the same as the FitAddModule command. possibly look into centralizing this functionality somewhere?
if module.fits(fit):
pyfalog.debug("Adding {} as module for fit {}", module, fit)
module.owner = fit
numSlots = len(fit.modules)
fit.modules.append(module)
if module.isValidState(State.ACTIVE):
module.state = State.ACTIVE
# todo: fix these
# As some items may affect state-limiting attributes of the ship, calculate new attributes first
# self.recalc(fit)
# Then, check states of all modules and change where needed. This will recalc if needed
sFit.checkStates(fit, module)
# fit.fill()
eos.db.commit()
self.change = numSlots != len(fit.modules)
self.new_position = module.modPosition
else:
return False
return True
def Undo(self):
# We added a subsystem module, which actually ran the replace command. Run the undo for that guy instead
if self.replace_cmd:
return self.replace_cmd.Undo()
from .fitRemoveModule import FitRemoveModuleCommand # Avoid circular import
if self.new_position is not None:
cmd = FitRemoveModuleCommand(self.fitID, [self.new_position])
cmd.Do()
return True

View File

@@ -23,6 +23,13 @@ class FitReplaceModuleCommand(wx.Command):
self.old_module = None
def Do(self):
fit = eos.db.getFit(self.fitID)
mod = fit.modules[self.position]
if not mod.isEmpty:
self.old_module = ModuleInfoCache(mod.modPosition, mod.item.ID, mod.state, mod.charge, mod.baseItemID,
mod.mutaplasmidID)
return self.change_module(self.fitID, self.position, self.itemID)
def Undo(self):
@@ -30,7 +37,6 @@ class FitReplaceModuleCommand(wx.Command):
fit = eos.db.getFit(self.fitID)
fit.modules.toDummy(self.position)
return True
self.change_module(self.fitID, self.position, self.old_module.itemID)
self.module.state = self.old_module.state
self.module.charge = self.old_module.charge
@@ -52,10 +58,7 @@ class FitReplaceModuleCommand(wx.Command):
pyfalog.debug("Changing position of module from position ({0}) for fit ID: {1}", self.position, fitID)
item = eos.db.getItem(itemID, eager=("attributes", "group.category"))
mod = fit.modules[self.position]
if not mod.isEmpty:
self.old_module = ModuleInfoCache(mod.modPosition, mod.item.ID, mod.state, mod.charge, mod.baseItemID, mod.mutaplasmidID)
try:
self.module = Module(item)
@@ -75,6 +78,9 @@ class FitReplaceModuleCommand(wx.Command):
if self.module.isValidState(State.ACTIVE):
self.module.state = State.ACTIVE
if self.old_module.charge and self.module.isValidCharge(self.old_module.charge):
self.module.charge = self.old_module.charge
# Then, check states of all modules and change where needed. This will recalc if needed
# self.checkStates(fit, m)

View File

@@ -9,7 +9,7 @@ pyfalog = Logger(__name__)
class FitSetChargeCommand(wx.Command):
def __init__(self, fitID, positions, chargeID=None):
def __init__(self, fitID, positions, chargeID=None, projected=False):
# todo: determine if this command really should be used with a group of modules, or a simple per module basis
wx.Command.__init__(self, True, "Module Charge Add")
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
@@ -17,6 +17,7 @@ class FitSetChargeCommand(wx.Command):
self.fitID = fitID
self.chargeID = chargeID
self.positions = positions
self.projected = projected
self.cache = None
def Do(self):
@@ -29,7 +30,8 @@ class FitSetChargeCommand(wx.Command):
def __setAmmo(self, positions, chargeID):
fit = eos.db.getFit(self.fitID)
self.cache = {fit.modules[i].modPosition: fit.modules[i].chargeID for i in positions}
source = fit.modules if not self.projected else fit.projectedModules
self.cache = {source[i].modPosition: source[i].chargeID for i in positions}
ammo = eos.db.getItem(chargeID) if chargeID else None
if ammo is not None and not ammo.isCharge:
@@ -37,7 +39,7 @@ class FitSetChargeCommand(wx.Command):
result = False
for pos in positions:
mod = fit.modules[pos]
mod = source[pos]
if not mod.isEmpty and mod.isValidCharge(ammo):
pyfalog.debug("Set ammo {} for {} on fit {}", ammo, mod, self.fitID)
result = True

View File

@@ -15,9 +15,10 @@ class GuiModuleAddChargeCommand(wx.Command):
self.fitID = fitID
self.itemID = itemID
self.positions = [mod.modPosition for mod in modules]
self.projected = modules[0].isProjected
def Do(self):
if self.internal_history.Submit(FitSetChargeCommand(self.fitID, self.positions, self.itemID)):
if self.internal_history.Submit(FitSetChargeCommand(self.fitID, self.positions, self.itemID, self.projected)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True

View File

@@ -3,7 +3,9 @@ from service.fit import Fit
import gui.mainFrame
from gui import globalEvents as GE
from eos.saveddata.fit import ImplantLocation
from .calc.fitAddImplant import FitAddImplantCommand
from .calc.fitChangeImplantLocation import FitChangeImplantLocation
class GuiAddImplantCommand(wx.Command):
@@ -16,7 +18,7 @@ class GuiAddImplantCommand(wx.Command):
self.itemID = itemID
def Do(self):
if self.internal_history.Submit(FitAddImplantCommand(self.fitID, self.itemID)):
if self.internal_history.Submit(FitAddImplantCommand(self.fitID, self.itemID)) and self.internal_history.Submit(FitChangeImplantLocation(self.fitID, ImplantLocation.FIT)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True

View File

@@ -0,0 +1,30 @@
import wx
from service.fit import Fit
import gui.mainFrame
from gui import globalEvents as GE
from .calc.fitChangeImplantLocation import FitChangeImplantLocation
class GuiChangeImplantLocation(wx.Command):
def __init__(self, fitID, source):
wx.Command.__init__(self, True, "Implant Source Change")
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.sFit = Fit.getInstance()
self.internal_history = wx.CommandProcessor()
self.fitID = fitID
self.source = source
def Do(self):
if self.internal_history.Submit(FitChangeImplantLocation(self.fitID, self.source)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True
return False
def Undo(self):
for _ in self.internal_history.Commands:
self.internal_history.Undo()
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True

View File

@@ -0,0 +1,38 @@
import wx
import eos.db
import gui.mainFrame
from gui import globalEvents as GE
from .calc.fitImportMutatedModule import FitImportMutatedCommand
from service.fit import Fit
from logbook import Logger
pyfalog = Logger(__name__)
class GuiImportMutatedModuleCommand(wx.Command):
def __init__(self, fitID, baseItem, mutaItem, attrMap):
wx.Command.__init__(self, True, "Mutated Module Import: {} {} {}".format(baseItem, mutaItem, attrMap))
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
self.sFit = Fit.getInstance()
self.fitID = fitID
self.baseItem = baseItem
self.mutaItem = mutaItem
self.attrMap = attrMap
self.internal_history = wx.CommandProcessor()
def Do(self):
pyfalog.debug("{} Do()".format(self))
if self.internal_history.Submit(FitImportMutatedCommand(self.fitID, self.baseItem, self.mutaItem, self.attrMap)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="modadd"))
return True
return False
def Undo(self):
pyfalog.debug("{} Undo()".format(self))
for _ in self.internal_history.Commands:
self.internal_history.Undo()
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID, action="moddel"))
return True

View File

@@ -17,6 +17,7 @@ class GuiRemoveImplantCommand(wx.Command):
def Do(self):
if self.internal_history.Submit(FitRemoveImplantCommand(self.fitID, self.position)):
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True
return False
@@ -24,5 +25,6 @@ class GuiRemoveImplantCommand(wx.Command):
def Undo(self):
for _ in self.internal_history.Commands:
self.internal_history.Undo()
self.sFit.recalc(self.fitID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.fitID))
return True

View File

@@ -73,6 +73,7 @@ from service.fit import Fit
from service.port import EfsPort, IPortUser, Port
from service.settings import HTMLExportSettings, SettingsProvider
from service.update import Update
import gui.fitCommands as cmd
disableOverrideEditor = False
@@ -728,12 +729,18 @@ class MainFrame(wx.Frame):
def importFromClipboard(self, event):
clipboard = fromClipboard()
activeFit = self.getActiveFit()
try:
fits = Port().importFitFromBuffer(clipboard, self.getActiveFit())
importType, importData = Port().importFitFromBuffer(clipboard, activeFit)
# If it's mutated item - make sure there's at least base item specified
if importType == "MutatedItem":
# we've imported an Abyssal module, need to fire off the command to add it to the fit
self.command.Submit(cmd.GuiImportMutatedModuleCommand(activeFit, *importData[0]))
return # no need to do anything else
except:
pyfalog.error("Attempt to import failed:\n{0}", clipboard)
else:
self._openAfterImport(fits)
self._openAfterImport(importData)
def exportToClipboard(self, event):
CopySelectDict = {CopySelectDialog.copyFormatEft: self.clipboardEft,

View File

@@ -110,3 +110,9 @@ def roundToPrec(val, prec):
if int(val) == val:
val = int(val)
return val
def roundDec(val, prec):
if int(val) == val:
return int(val)
return round(val, prec)

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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