Merge branch 'test-3' into esi

# Conflicts:
#	eos/saveddata/character.py
#	service/character.py
#	service/eveapi.py
#	service/pycrest/eve.py
This commit is contained in:
blitzmann
2018-03-10 15:32:21 -05:00
113 changed files with 846 additions and 379 deletions

3
.gitignore vendored
View File

@@ -119,3 +119,6 @@ ENV/
eos.iml
gitversion
.version
/.version
*.swp

View File

@@ -1 +0,0 @@
v1.2.3-221-g50dd74db

View File

@@ -23,10 +23,11 @@ debug = False
saveInRoot = False
# Version data
version = "1.34.0"
tag = "Stable"
expansionName = " Arms Race"
expansionVersion = "1.3"
version = "2.0.0b4"
tag = "git"
expansionName = "YC120.2"
expansionVersion = "1.2"
evemonMinVersion = "4081"
pyfaPath = None
@@ -76,10 +77,13 @@ def getPyfaRoot():
return root
def getGitVersion():
with open(os.path.join(pyfaPath, '.version')) as f:
version = f.readline()
return version
def getVersion():
if os.path.isfile(os.path.join(pyfaPath, '.version')):
with open(os.path.join(pyfaPath, '.version')) as f:
gitVersion = f.readline()
return gitVersion
# if no version file exists, then user is running from source or not an official build
return version + " (git)"
def getDefaultSave():
@@ -136,7 +140,7 @@ def defPaths(customSavePath=None):
# os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem")
# The database where we store all the fits etc
saveDB = os.path.join(savePath, "saveddata.db")
saveDB = os.path.join(savePath, "saveddata-py3-dev.db")
# The database where the static EVE data from the datadump is kept.
# This is not the standard sqlite datadump but a modified version created by eos

View File

@@ -62,7 +62,7 @@ exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
debug=False,
console=True,
console=False,
strip=False,
upx=True,
name='pyfa',

View File

@@ -0,0 +1,45 @@
# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(1, 15, 1, 0),
prodvers=(1, 15, 1, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x40004,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
StringFileInfo(
[
StringTable(
u'040904E4',
[StringStruct(u'LegalCopyright', u''),
StringStruct(u'InternalName', u'pyfa.exe'),
StringStruct(u'FileVersion', u'1.15.1.0'),
StringStruct(u'CompanyName', u''),
StringStruct(u'OriginalFilename', u'pyfa.exe'),
StringStruct(u'ProductVersion', u'1.15.1.0'),
StringStruct(u'FileDescription', u'Python fitting assistant'),
StringStruct(u'LegalTrademarks', u''),
StringStruct(u'Comments', u''),
StringStruct(u'ProductName', u'pyfa')])
]),
VarFileInfo([VarStruct(u'Translation', [1033, 1252])])
]
)

View File

@@ -18,7 +18,7 @@ if istravis is True or hasattr(sys, '_called_from_test'):
# Running in Travis. Run saveddata database in memory.
saveddata_connectionstring = 'sqlite:///:memory:'
else:
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db"))
saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata-py3-db.db"))
pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring)

View File

@@ -68,7 +68,7 @@ class DefaultDatabaseValues(object):
["[Hybrid Charges]Uranium", "0", "38.4", "57.6", "0"],
["[Missiles]Mjolnir", "100", "0", "0", "0"], ["[Missiles]Inferno", "0", "100", "0", "0"],
["[Missiles]Scourge", "0", "0", "100", "0"], ["[Missiles]Nova", "0", "0", "0", "100"],
["[Missiles][Structure) Standup Missile", "100", "100", "100", "100"],
["[Missiles][Structure] Standup Missile", "100", "100", "100", "100"],
["[Projectile Ammo][T2] Tremor", "0", "0", "24", "40"],
["[Projectile Ammo][T2] Quake", "0", "0", "40", "72"],
["[Projectile Ammo][T2] Hail", "0", "0", "26.4", "96.8"],

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Modules named like: Memetic Algorithm Bank (8 of 8)
# Implant: Neural Lace 'Blackglass' Net Intrusion 920-40
# Implant: Poteque 'Prospector' Environmental Analysis EY-1005
# Implant: Poteque 'Prospector' Hacking HC-905
type = "passive"

View File

@@ -1,7 +1,7 @@
# ammoInfluenceCapNeed
#
# Used by:
# Items from category: Charge (478 of 924)
# Items from category: Charge (478 of 925)
type = "passive"

View File

@@ -1,7 +1,7 @@
# ammoInfluenceRange
#
# Used by:
# Items from category: Charge (572 of 924)
# Items from category: Charge (572 of 925)
type = "passive"

View File

@@ -1,7 +1,7 @@
# ammoSpeedMultiplier
#
# Used by:
# Charges from group: Festival Charges (22 of 22)
# Charges from group: Festival Charges (23 of 23)
# Charges from group: Interdiction Probe (2 of 2)
type = "passive"

View File

@@ -0,0 +1,21 @@
# Not used by any item
type = "passive"
runTime = "early"
def handler(fit, src, context):
for attr in [
"structureRigDoomsdayDamageLossTargetBonus",
"structureRigScanResBonus",
"structureRigPDRangeBonus",
"structureRigPDCapUseBonus",
"structureRigMissileExploVeloBonus",
"structureRigMissileVelocityBonus",
"structureRigEwarOptimalBonus",
"structureRigEwarFalloffBonus",
"structureRigEwarCapUseBonus",
"structureRigMissileExplosionRadiusBonus"
]:
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Jury Rigging"),
attr, src.getModifiedItemAttr("structureRoleBonus"))

View File

@@ -1,8 +1,7 @@
# damageControl
#
# Used by:
# Variations of module: Damage Control I (16 of 16)
# Module: Civilian Damage Control
# Modules from group: Damage Control (22 of 27)
type = "passive"

View File

@@ -1,7 +1,7 @@
# doHacking
#
# Used by:
# Modules from group: Data Miners (17 of 18)
# Modules from group: Data Miners (9 of 9)
type = "active"

View File

@@ -0,0 +1,9 @@
# doomsdayAOEBubble
#
# Used by:
# Module: Standup Warp Disruption Burst Projector
type = "projected", "active"
def handler(fit, module, context):
return

View File

@@ -0,0 +1,16 @@
# doomsdayAOEDamp
#
# Used by:
# Module: Sensor Dampening Burst Projector
type = "projected", "active"
def handler(fit, module, context, *args, **kwargs):
if "projected" not in context:
return
fit.ship.boostItemAttr("maxTargetRange", module.getModifiedItemAttr("maxTargetRangeBonus"),
stackingPenalties=True, *args, **kwargs)
fit.ship.boostItemAttr("scanResolution", module.getModifiedItemAttr("scanResolutionBonus"),
stackingPenalties=True, *args, **kwargs)

View File

@@ -0,0 +1,21 @@
# doomsdayAOENeut
#
# Used by:
# Module: Energy Neutralization Burst Projector
from eos.saveddata.module import State
from eos.modifiedAttributeDict import ModifiedAttributeDict
type = "active", "projected"
def handler(fit, src, context, **kwargs):
if "projected" in context and ((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

@@ -0,0 +1,11 @@
# doomsdayAOEPaint
#
# Used by:
# Module: Target Illumination Burst Projector
type = "projected", "active"
def handler(fit, container, context, *args, **kwargs):
if "projected" in context:
fit.ship.boostItemAttr("signatureRadius", container.getModifiedItemAttr("signatureRadiusBonus"),
stackingPenalties=True, *args, **kwargs)

View File

@@ -0,0 +1,29 @@
# doomsdayAOETrack
#
# Used by:
# Module: Weapon Disruption Burst Projector
type = "active", "projected"
def handler(fit, module, context, *args, **kwargs):
if "projected" in context:
for srcAttr, tgtAttr in (
("aoeCloudSizeBonus", "aoeCloudSize"),
("aoeVelocityBonus", "aoeVelocity"),
("missileVelocityBonus", "maxVelocity"),
("explosionDelayBonus", "explosionDelay"),
):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"),
tgtAttr, module.getModifiedItemAttr(srcAttr),
stackingPenalties=True, *args, **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
"trackingSpeed", module.getModifiedItemAttr("trackingSpeedBonus"),
stackingPenalties=True, *args, **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
"maxRange", module.getModifiedItemAttr("maxRangeBonus"),
stackingPenalties=True, *args, **kwargs)
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Gunnery"),
"falloff", module.getModifiedItemAttr("falloffBonus"),
stackingPenalties=True, *args, **kwargs)

View File

@@ -0,0 +1,12 @@
# doomsdayAOEWeb
#
# Used by:
# Module: Stasis Webification Burst Projector
type = "active", "projected"
def handler(fit, module, context, *args, **kwargs):
if "projected" not in context:
return
fit.ship.boostItemAttr("maxVelocity", module.getModifiedItemAttr("speedFactor"),
stackingPenalties=True, *args, **kwargs)

View File

@@ -1,7 +1,4 @@
# eliteBonusGunshipDroneCapacity2
#
# Used by:
# Ship: Ishkur
# Not used by any item
type = "passive"

View File

@@ -0,0 +1,7 @@
# Not used by any item
type = "passive"
def handler(fit, src, context):
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "trackingSpeed",
src.getModifiedItemAttr("eliteBonusGunship2"), stackingPenalties=True, skill="Assault Frigates")

View File

@@ -0,0 +1,10 @@
# eliteBonusGunshipEMMissileDamage1
#
# Used by:
# Ship: Jaguar
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), "emDamage",
src.getModifiedItemAttr("eliteBonusGunship1"), skill="Assault Frigates")

View File

@@ -0,0 +1,10 @@
# eliteBonusGunshipExplosionVelocity2
#
# Used by:
# Ship: Jaguar
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), "aoeVelocity",
src.getModifiedItemAttr("eliteBonusGunship2"), stackingPenalties=True, skill="Assault Frigates")

View File

@@ -0,0 +1,10 @@
# eliteBonusGunshipExplosiveMissileDamage1
#
# Used by:
# Ship: Jaguar
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), "explosiveDamage",
src.getModifiedItemAttr("eliteBonusGunship1"), skill="Assault Frigates")

View File

@@ -0,0 +1,10 @@
# eliteBonusGunshipKineticMissileDamage1
#
# Used by:
# Ship: Jaguar
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), "kineticDamage",
src.getModifiedItemAttr("eliteBonusGunship1"), skill="Assault Frigates")

View File

@@ -1,7 +1,4 @@
# eliteBonusGunshipProjectileDamage2
#
# Used by:
# Ship: Jaguar
# Not used by any item
type = "passive"

View File

@@ -1,7 +1,4 @@
# eliteBonusGunshipProjectileOptimal1
#
# Used by:
# Ship: Jaguar
# Not used by any item
type = "passive"

View File

@@ -0,0 +1,10 @@
# eliteBonusGunshipThermalMissileDamage1
#
# Used by:
# Ship: Jaguar
type = "passive"
def handler(fit, src, context):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.requiresSkill("Missile Launcher Operation"), "thermalDamage",
src.getModifiedItemAttr("eliteBonusGunship1"), skill="Assault Frigates")

View File

@@ -14,4 +14,5 @@ grouped = True
def handler(fit, src, context):
if "projected" not in context:
return
fit.ship.boostItemAttr("maxVelocity", src.getModifiedItemAttr("{}SpeedPenalty".format(prefix)) * src.amountActive)
fit.ship.boostItemAttr("maxVelocity", src.getModifiedItemAttr("{}SpeedPenalty".format(prefix)) * src.amountActive,
stackingPenalties=True)

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Modules named like: Memetic Algorithm Bank (8 of 8)
# Implant: Neural Lace 'Blackglass' Net Intrusion 920-40
# Implant: Poteque 'Prospector' Environmental Analysis EY-1005
# Implant: Poteque 'Prospector' Hacking HC-905
# Skill: Hacking

View File

@@ -1,4 +1,7 @@
# Not used by any item
# hackingVirusStrengthBonus
#
# Used by:
# Implant: Neural Lace 'Blackglass' Net Intrusion 920-40
type = "passive"

View File

@@ -0,0 +1,16 @@
# moduleBonusAssaultDamageControl
#
# Used by:
# Variations of module: Assault Damage Control I (5 of 5)
type = "active"
runTime = "early"
def handler(fit, src, context):
for layer, attrPrefix in (('shield', 'shield'), ('armor', 'armor'), ('hull', '')):
for damageType in ('Kinetic', 'Thermal', 'Explosive', 'Em'):
bonus = "%s%sDamageResonance" % (attrPrefix, damageType)
bonus = "%s%s" % (bonus[0].lower(), bonus[1:])
booster = "%s%sDamageResonance" % (layer, damageType)
src.forceItemAttr(booster, src.getModifiedItemAttr("resistanceMultiplier"))

View File

@@ -0,0 +1,10 @@
# remoteWebifierMaxRangeBonus
#
# Used by:
# Implants named like: Inquest 'Eros' Stasis Webifier MR (3 of 3)
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Stasis Web", "maxRange",
src.getModifiedItemAttr("stasisWebRangeBonus"), stackingPenalties=True)

View File

@@ -0,0 +1,10 @@
# scriptscanGravimetricStrengthBonusBonus
#
# Used by:
# Charges from group: Structure ECM script (4 of 4)
type = "passive"
runTime = "early"
def handler(fit, src, context, *args, **kwargs):
src.boostItemAttr("scanGravimetricStrengthBonus", src.getModifiedChargeAttr("scanGravimetricStrengthBonusBonus"))

View File

@@ -0,0 +1,10 @@
# scriptscanLadarStrengthBonusBonus
#
# Used by:
# Charges from group: Structure ECM script (4 of 4)
type = "passive"
runTime = "early"
def handler(fit, src, context, *args, **kwargs):
src.boostItemAttr("scanLadarStrengthBonus", src.getModifiedChargeAttr("scanLadarStrengthBonusBonus"))

View File

@@ -0,0 +1,10 @@
# scriptscanMagnetometricStrengthBonusBonus
#
# Used by:
# Charges from group: Structure ECM script (4 of 4)
type = "passive"
runTime = "early"
def handler(fit, src, context, *args, **kwargs):
src.boostItemAttr("scanMagnetometricStrengthBonus", src.getModifiedChargeAttr("scanMagnetometricStrengthBonusBonus"))

View File

@@ -0,0 +1,10 @@
# scriptscanRadarStrengthBonusBonus
#
# Used by:
# Charges from group: Structure ECM script (4 of 4)
type = "passive"
runTime = "early"
def handler(fit, src, context, *args, **kwargs):
src.boostItemAttr("scanRadarStrengthBonus", src.getModifiedChargeAttr("scanRadarStrengthBonusBonus"))

View File

@@ -0,0 +1,11 @@
# scriptStandupWarpScram
#
# Used by:
# Charge: Standup Focused Warp Scrambling Script
type = "passive"
runTime = "early"
def handler(fit, src, context, *args, **kwargs):
src.boostItemAttr("maxRange", src.getModifiedChargeAttr("warpScrambleRangeBonus"))

View File

@@ -0,0 +1,7 @@
# Not used by any item
type = "passive"
runTime = "early"
def handler(fit, src, context):
fit.ship.forceItemAttr("structureFullPowerStateHitpointMultiplier", src.getModifiedItemAttr("serviceModuleFullPowerStateHitpointMultiplier"))

View File

@@ -0,0 +1,10 @@
# shipBonusDroneTrackingEliteGunship2
#
# Used by:
# Ship: Ishkur
type = "passive"
def handler(fit, src, context):
fit.drones.filteredItemBoost(lambda mod: mod.item.requiresSkill("Drones"), "trackingSpeed",
src.getModifiedItemAttr("eliteBonusGunship2"), skill="Assault Frigates")

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Breacher
# Ship: Jaguar
type = "passive"

View File

@@ -1,11 +1,12 @@
# shipPDmgBonusMF
#
# Used by:
# Variations of ship: Rifter (3 of 3)
# Variations of ship: Slasher (3 of 3)
# Ship: Cheetah
# Ship: Freki
# Ship: Republic Fleet Firetail
# Ship: Rifter
# Ship: Wolf
type = "passive"

View File

@@ -2,7 +2,6 @@
#
# Used by:
# Variations of ship: Slasher (3 of 3)
# Ship: Jaguar
# Ship: Republic Fleet Firetail
# Ship: Wolf
type = "passive"

View File

@@ -0,0 +1,10 @@
# shipSETROFAF
#
# Used by:
# Ship: Retribution
type = "passive"
def handler(fit, src, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Small Energy Turret"), "speed",
src.getModifiedItemAttr("shipBonusAF"), stackingPenalties=False, skill="Amarr Frigate")

View File

@@ -1,7 +1,4 @@
# shipSETTrackingBonusAF
#
# Used by:
# Ship: Retribution
# Not used by any item
type = "passive"

View File

@@ -2,6 +2,7 @@
#
# Used by:
# Ship: Breacher
# Ship: Jaguar
type = "passive"

View File

@@ -0,0 +1,11 @@
# Not used by any item
type = "passive"
def handler(fit, ship, context):
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Structure Guided Bomb Launcher",
"speed", ship.getModifiedItemAttr("structureAoERoFRoleBonus"))
for attr in ["duration", "durationTargetIlluminationBurstProjector", "durationWeaponDisruptionBurstProjector",
"durationECMJammerBurstProjector", "durationSensorDampeningBurstProjector", "capacitorNeed"]:
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Structure Burst Projector",
attr, ship.getModifiedItemAttr("structureAoERoFRoleBonus"))

View File

@@ -0,0 +1,7 @@
# Not used by any item
type = "passive"
runTime = "early"
def handler(fit, src, context):
fit.ship.multiplyItemAttr("hiddenArmorHPMultiplier", src.getModifiedItemAttr("armorHPMultiplier"))

View File

@@ -11,7 +11,7 @@ def handler(fit, module, context):
module.getModifiedItemAttr("missileDamageMultiplierBonus"),
stackingPenalties=True)
launcherGroups = ("Structure AXL Missile Launcher", "Structure ASML Missile Launcher")
launcherGroups = ("Structure XL Missile Launcher", "Structure Multirole Missile Launcher")
fit.modules.filteredItemMultiply(lambda mod: mod.item.group.name in launcherGroups,
"speed", module.getModifiedItemAttr("speedMultiplier"),
stackingPenalties=True)

View File

@@ -0,0 +1,6 @@
# Not used by any item
type = "passive"
def handler(fit, ship, context):
fit.ship.increaseItemAttr("capacitorCapacity", ship.getModifiedItemAttr("capacitorBonus"))

View File

@@ -0,0 +1,7 @@
# Not used by any item
type = "passive"
def handler(fit, src, context):
fit.ship.multiplyItemAttr("shieldCapacity", src.getModifiedItemAttr("structureFullPowerStateHitpointMultiplier") or 0)
fit.ship.multiplyItemAttr("armorHP", src.getModifiedItemAttr("structureFullPowerStateHitpointMultiplier") or 0)

View File

@@ -0,0 +1,6 @@
# Not used by any item
type = "passive"
def handler(fit, src, context):
fit.ship.multiplyItemAttr("armorHP", src.getModifiedItemAttr("hiddenArmorHPMultiplier") or 0)

View File

@@ -0,0 +1,10 @@
# Not used by any item
type = "passive"
def handler(fit, src, context):
groups = ("Structure Anti-Subcapital Missile", "Structure Anti-Capital Missile")
for dmgType in ("em", "kinetic", "explosive", "thermal"):
fit.modules.filteredChargeMultiply(lambda mod: mod.item.group.name in groups,
"%sDamage" % dmgType,
src.getModifiedItemAttr("hiddenMissileDamageMultiplier"))

View File

@@ -0,0 +1,15 @@
# Not used by any item
type = "passive"
def handler(fit, container, context):
missileGroups = ("Structure Anti-Capital Missile", "Structure Anti-Subcapital Missile")
for srcAttr, tgtAttr in (
("aoeCloudSizeBonus", "aoeCloudSize"),
("aoeVelocityBonus", "aoeVelocity"),
("missileVelocityBonus", "maxVelocity"),
("explosionDelayBonus", "explosionDelay"),
):
fit.modules.filteredChargeBoost(lambda mod: mod.charge.group.name in missileGroups,
tgtAttr, container.getModifiedItemAttr(srcAttr),
stackingPenalties=True)

View File

@@ -0,0 +1,6 @@
# Not used by any item
type = "passive"
def handler(fit, module, context):
fit.ship.multiplyItemAttr("rechargeRate", module.getModifiedItemAttr("capacitorRechargeRateMultiplier"))

View File

@@ -0,0 +1,6 @@
# Not used by any item
type = "passive"
def handler(fit, module, context):
fit.ship.boostItemAttr("maxTargetRange", module.getModifiedItemAttr("structureRigMaxTargetRangeBonus"))

View File

@@ -7,12 +7,12 @@ type = "projected", "active"
def handler(fit, module, context):
if "projected" not in context:
return
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
# this is such a dirty hack
for mod in fit.modules:
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if "projected" in context:
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
if module.charge is not None and module.charge.ID == 47336:
for mod in fit.modules:
if not mod.isEmpty and mod.state > State.ONLINE and (
mod.item.requiresSkill("High Speed Maneuvering")
or mod.item.requiresSkill("Micro Jump Drive Operation")
):
mod.state = State.ONLINE

View File

@@ -1,3 +1,4 @@
# warpDisruptSphere
#
# Used by:
@@ -9,18 +10,23 @@ runTime = "early"
def handler(fit, module, context):
fit.ship.boostItemAttr("mass", module.getModifiedItemAttr("massBonusPercentage"))
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"))
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Propulsion Module",
"speedBoostFactor", module.getModifiedItemAttr("speedBoostFactorBonus"))
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Propulsion Module",
"speedFactor", module.getModifiedItemAttr("speedFactorBonus"))
fit.ship.forceItemAttr("disallowAssistance", 1)
if "projected" in context:
fit.ship.increaseItemAttr("warpScrambleStatus", module.getModifiedItemAttr("warpScrambleStrength"))
if module.charge is not None and module.charge.ID == 45010:
for mod in fit.modules:
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
mod.state = State.ONLINE
if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE:
mod.state = State.ONLINE
else:
if module.charge is None:
fit.ship.boostItemAttr("mass", module.getModifiedItemAttr("massBonusPercentage"))
fit.ship.boostItemAttr("signatureRadius", module.getModifiedItemAttr("signatureRadiusBonus"))
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Propulsion Module",
"speedBoostFactor", module.getModifiedItemAttr("speedBoostFactorBonus"))
fit.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Propulsion Module",
"speedFactor", module.getModifiedItemAttr("speedFactorBonus"))
fit.ship.forceItemAttr("disallowAssistance", 1)

View File

@@ -16,5 +16,8 @@ def handler(fit, module, context):
# this is such a dirty hack
for mod in fit.modules:
if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE:
if not mod.isEmpty and mod.state > State.ONLINE and (
mod.item.requiresSkill("Micro Jump Drive Operation")
or mod.item.requiresSkill("High Speed Maneuvering")
):
mod.state = State.ONLINE

View File

@@ -81,7 +81,7 @@ class FitDpsGraph(Graph):
total += dps * self.calculateTurretMultiplier(mod, data)
elif mod.hardpoint == Hardpoint.MISSILE:
if mod.state >= State.ACTIVE and mod.maxRange >= distance:
if mod.state >= State.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
total += dps * self.calculateMissileMultiplier(mod, data)
if distance <= fit.extraAttributes["droneControlRange"]:

View File

@@ -18,12 +18,16 @@
# ===============================================================================
import re
import eos.db
class DamagePattern(object):
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
def __init__(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def update(self, emAmount=25, thermalAmount=25, kineticAmount=25, explosiveAmount=25):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
self.kineticAmount = kineticAmount
@@ -74,6 +78,14 @@ class DamagePattern(object):
lines = re.split('[\n\r]+', text)
patterns = []
numPatterns = 0
# When we import damage profiles, we create new ones and update old ones. To do this, get a list of current
# patterns to allow lookup
lookup = {}
current = eos.db.getDamagePatternList()
for pattern in current:
lookup[pattern.name] = pattern
for line in lines:
try:
if line.strip()[0] == "#": # comments
@@ -99,10 +111,18 @@ class DamagePattern(object):
continue
if len(fields) == 4: # Avoid possible blank lines
pattern = DamagePattern(**fields)
pattern.name = name.strip()
if name.strip() in lookup:
pattern = lookup[name.strip()]
pattern.update(**fields)
eos.db.save(pattern)
else:
pattern = DamagePattern(**fields)
pattern.name = name.strip()
eos.db.save(pattern)
patterns.append(pattern)
eos.db.commit()
return patterns, numPatterns
EXPORT_FORMAT = "DamageProfile = %s,%d,%d,%d,%d\n"

View File

@@ -264,7 +264,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def fits(self, fit):
fitDroneGroupLimits = set()
for i in range(1, 3):
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i)
groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i, None)
if groneGrp is not None:
fitDroneGroupLimits.add(int(groneGrp))
if len(fitDroneGroupLimits) == 0:

View File

@@ -89,8 +89,8 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes.overrides = self.__item.overrides
self.__slot = self.__calculateSlot(self.__item)
chargeID = self.getModifiedItemAttr("fighterAbilityLaunchBombType", None)
if chargeID is not None:
chargeID = self.getModifiedItemAttr("fighterAbilityLaunchBombType")
if chargeID:
charge = eos.db.getItem(int(chargeID))
self.__charge = charge
self.__chargeModifiedAttributes.original = charge.attributes
@@ -104,7 +104,10 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
types = {
"Light" : Slot.F_LIGHT,
"Support": Slot.F_SUPPORT,
"Heavy" : Slot.F_HEAVY
"Heavy" : Slot.F_HEAVY,
"StandupLight": Slot.FS_LIGHT,
"StandupSupport": Slot.FS_SUPPORT,
"StandupHeavy": Slot.FS_HEAVY
}
for t, slot in types.items():

View File

@@ -917,7 +917,7 @@ class Fit(object):
for mod in chain(self.modules, self.fighters):
if mod.slot is type and (not getattr(mod, "isEmpty", False) or countDummies):
if type in (Slot.F_HEAVY, Slot.F_SUPPORT, Slot.F_LIGHT) and not mod.active:
if type in (Slot.F_HEAVY, Slot.F_SUPPORT, Slot.F_LIGHT, Slot.FS_HEAVY, Slot.FS_LIGHT, Slot.FS_SUPPORT) and not mod.active:
continue
amount += 1
@@ -932,7 +932,10 @@ class Fit(object):
Slot.SERVICE : "serviceSlots",
Slot.F_LIGHT : "fighterLightSlots",
Slot.F_SUPPORT: "fighterSupportSlots",
Slot.F_HEAVY : "fighterHeavySlots"
Slot.F_HEAVY : "fighterHeavySlots",
Slot.FS_LIGHT: "fighterStandupLightSlots",
Slot.FS_SUPPORT: "fighterStandupSupportSlots",
Slot.FS_HEAVY: "fighterStandupHeavySlots",
}
def getSlotsFree(self, type, countDummies=False):

View File

@@ -56,6 +56,10 @@ class Slot(Enum):
F_LIGHT = 10
F_SUPPORT = 11
F_HEAVY = 12
# fighter 'slots' (for structures)
FS_LIGHT = 13
FS_SUPPORT = 14
FS_HEAVY = 15
class Hardpoint(Enum):
@@ -178,7 +182,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def numShots(self):
if self.charge is None:
return -1
return 0
if self.__chargeCycles is None and self.charge:
numCharges = self.numCharges
# Usual ammo like projectiles and missiles
@@ -430,9 +434,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
and fit.ship.item.ID not in fitsOnType:
return False
# AFAIK Citadel modules will always be restricted based on canFitShipType/Group. If we are fitting to a Citadel
# and the module does not have these properties, return false to prevent regular ship modules from being used
if isinstance(fit.ship, Citadel) and len(fitsOnGroup) == 0 and len(fitsOnType) == 0:
# Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
if isinstance(fit.ship, Citadel) and self.item.category.name != "Structure Module" or \
not isinstance(fit.ship, Citadel) and self.item.category.name == "Structure Module":
return False
# EVE doesn't let capital modules be fit onto subcapital hulls. Confirmed by CCP Larrikin that this is dictated

View File

@@ -19,6 +19,7 @@
import re
from logbook import Logger
import eos.db
pyfalog = Logger(__name__)
@@ -27,7 +28,10 @@ class TargetResists(object):
# also determined import/export order - VERY IMPORTANT
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
def __init__(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0):
def __init__(self, *args, **kwargs):
self.update(*args, **kwargs)
def update(self, emAmount=0, thermalAmount=0, kineticAmount=0, explosiveAmount=0):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
self.kineticAmount = kineticAmount
@@ -38,6 +42,14 @@ class TargetResists(object):
lines = re.split('[\n\r]+', text)
patterns = []
numPatterns = 0
# When we import damage profiles, we create new ones and update old ones. To do this, get a list of current
# patterns to allow lookup
lookup = {}
current = eos.db.getTargetResistsList()
for pattern in current:
lookup[pattern.name] = pattern
for line in lines:
try:
if line.strip()[0] == "#": # comments
@@ -66,10 +78,18 @@ class TargetResists(object):
continue
if len(fields) == 4: # Avoid possible blank lines
pattern = TargetResists(**fields)
pattern.name = name.strip()
if name.strip() in lookup:
pattern = lookup[name.strip()]
pattern.update(**fields)
eos.db.save(pattern)
else:
pattern = TargetResists(**fields)
pattern.name = name.strip()
eos.db.save(pattern)
patterns.append(pattern)
eos.db.commit()
return patterns, numPatterns
EXPORT_FORMAT = "TargetResists = %s,%.1f,%.1f,%.1f,%.1f\n"

BIN
eve.db

Binary file not shown.

View File

@@ -51,6 +51,7 @@ class BoosterView(d.Display):
"Base Name",
"Side Effects",
"Price",
"Miscellanea",
]
def __init__(self, parent):
@@ -130,6 +131,7 @@ class BoosterView(d.Display):
fit = sFit.getFit(fitID)
if not fit or fit.isStructure:
event.Skip()
return
trigger = sFit.addBooster(fitID, event.itemID)

View File

@@ -210,6 +210,7 @@ class DroneView(Display):
fit = sFit.getFit(fitID)
if not fit or fit.isStructure:
event.Skip()
return
trigger = sFit.addDrone(fitID, event.itemID)

View File

@@ -91,7 +91,10 @@ class FighterView(wx.Panel):
if fit:
for x in self.labels:
slot = getattr(Slot, "F_{}".format(x.upper()))
if fit.isStructure:
slot = getattr(Slot, "FS_{}".format(x.upper()))
else:
slot = getattr(Slot, "F_{}".format(x.upper()))
used = fit.getSlotsUsed(slot)
total = fit.getNumSlots(slot)
color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings.GetColour(

View File

@@ -152,6 +152,7 @@ class ImplantDisplay(d.Display):
fit = sFit.getFit(fitID)
if not fit or fit.isStructure:
event.Skip()
return
trigger = sFit.addImplant(fitID, event.itemID)

View File

@@ -99,15 +99,22 @@ class ProjectedView(d.Display):
data[0] is hard-coded str of originating source
data[1] is typeID or index of data we want to manipulate
"""
sFit = Fit.getInstance()
fit = sFit.getFit(self.mainFrame.getActiveFit())
if data[0] == "projected":
# if source is coming from projected, we are trying to combine drones.
self.mergeDrones(x, y, int(data[1]))
elif data[0] == "fitting":
dstRow, _ = self.HitTest((x, y))
# Gather module information to get position
module = fit.modules[int(data[1])]
sFit.project(fit.ID, module.item.ID)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
elif data[0] == "market":
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
sFit.project(fitID, int(data[1]))
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit()))
sFit.project(fit.ID, int(data[1]))
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fit.ID))
def kbEvent(self, event):
keycode = event.GetKeyCode()

View File

@@ -26,57 +26,71 @@ class ChangeAmount(ContextMenu):
return u"Change {0} Quantity".format(itmContext)
def activate(self, fullContext, selection, i):
srcContext = fullContext[0]
dlg = AmountChanger(self.mainFrame, selection[0], srcContext)
dlg.ShowModal()
thing = selection[0]
mainFrame = gui.mainFrame.MainFrame.getInstance()
fitID = mainFrame.getActiveFit()
if isinstance(thing, es_Fit):
value = thing.getProjectionInfo(fitID).amount
else:
value = thing.amount
dlg = AmountChanger(self.mainFrame, value)
if dlg.ShowModal() == wx.ID_OK:
if dlg.input.GetLineText(0).strip() == '':
return
sFit = Fit.getInstance()
cleanInput = re.sub(r'[^0-9.]', '', dlg.input.GetLineText(0).strip())
if isinstance(thing, es_Cargo):
sFit.addCargo(fitID, thing.item.ID, int(float(cleanInput)), replace=True)
elif isinstance(thing, es_Fit):
sFit.changeAmount(fitID, thing, int(float(cleanInput)))
elif isinstance(thing, es_Fighter):
sFit.changeActiveFighters(fitID, thing, int(float(cleanInput)))
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
ChangeAmount.register()
class AmountChanger(wx.Dialog):
def __init__(self, parent, thing, context):
wx.Dialog.__init__(self, parent, title="Select Amount", size=wx.Size(220, 60))
self.thing = thing
self.context = context
def __init__(self, parent, value):
wx.Dialog.__init__(self, parent, title="Change Amount")
self.SetMinSize((346, 156))
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
bSizer1 = wx.BoxSizer(wx.VERTICAL)
bSizer2 = wx.BoxSizer(wx.VERTICAL)
text = wx.StaticText(self, wx.ID_ANY, "New Amount:")
bSizer2.Add(text, 0)
bSizer1.Add(bSizer2, 0, wx.ALL, 10)
self.input = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.input.SetValue(str(value))
bSizer1.Add(self.input, 1, wx.ALL, 5)
bSizer1.Add(self.input, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 15)
bSizer3 = wx.BoxSizer(wx.VERTICAL)
bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 15)
bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND)
bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10)
self.input.SetFocus()
self.input.SetInsertionPointEnd()
self.input.Bind(wx.EVT_CHAR, self.onChar)
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
self.button = wx.Button(self, wx.ID_OK, "Done")
bSizer1.Add(self.button, 0, wx.ALL, 5)
self.input.Bind(wx.EVT_TEXT_ENTER, self.processEnter)
self.SetSizer(bSizer1)
self.Layout()
self.Centre(wx.BOTH)
self.button.Bind(wx.EVT_BUTTON, self.change)
self.CenterOnParent()
self.Fit()
def change(self, event):
if self.input.GetLineText(0).strip() == '':
event.Skip()
self.Close()
return
sFit = Fit.getInstance()
cleanInput = re.sub(r'[^0-9.]', '', self.input.GetLineText(0).strip())
mainFrame = gui.mainFrame.MainFrame.getInstance()
fitID = mainFrame.getActiveFit()
if isinstance(self.thing, es_Cargo):
sFit.addCargo(fitID, self.thing.item.ID, int(float(cleanInput)), replace=True)
elif isinstance(self.thing, es_Fit):
sFit.changeAmount(fitID, self.thing, int(float(cleanInput)))
elif isinstance(self.thing, es_Fighter):
sFit.changeActiveFighters(fitID, self.thing, int(float(cleanInput)))
wx.PostEvent(mainFrame, GE.FitChanged(fitID=fitID))
event.Skip()
self.Close()
def processEnter(self, evt):
self.EndModal(wx.ID_OK)
# checks to make sure it's valid number
@staticmethod

View File

@@ -31,9 +31,7 @@ class FactorReload(ContextMenu):
def getBitmap(self, context, selection):
sFit = Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(fitID)
if fit.factorReload:
if sFit.serviceFittingOptions["useGlobalForceReload"]:
return BitmapLoader.getBitmap("state_active_small", "gui")
else:
return None

View File

@@ -243,6 +243,8 @@ class ItemParams(wx.Panel):
return "%s (%d)" % (group.name, value) if group is not None else str(value)
def attributeIDCallback():
if not value: # some attributes come through with a value of 0? See #1387
return "%d" % (value)
attribute = Attribute.getInstance().getAttributeInfo(value)
return "%s (%d)" % (attribute.name.capitalize(), value)

View File

@@ -42,6 +42,15 @@ class PFGeneralPref(PreferenceView):
self.inputLogPath.SetBackgroundColour((200, 200, 200))
mainSizer.Add(self.inputLogPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
import requests
self.certPath = wx.StaticText(panel, wx.ID_ANY, "Cert Path:", wx.DefaultPosition, wx.DefaultSize, 0)
self.certPath .Wrap(-1)
mainSizer.Add(self.certPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
self.certPathCtrl = wx.TextCtrl(panel, wx.ID_ANY, requests.certs.where(), wx.DefaultPosition, wx.DefaultSize, 0)
self.certPathCtrl.SetEditable(False)
self.certPathCtrl.SetBackgroundColour((200, 200, 200))
mainSizer.Add(self.certPathCtrl, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5)
# Debug Logging
self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, "Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0)
mainSizer.Add(self.cbdebugLogging, 0, wx.ALL | wx.EXPAND, 5)

View File

@@ -121,11 +121,11 @@ class PFNetworkPref(PreferenceView):
self.stPSetLogin = wx.StaticText(panel, wx.ID_ANY, "Username:", wx.DefaultPosition, wx.DefaultSize, 0)
self.stPSetLogin.Wrap(-1)
self.editProxySettingsLogin = wx.TextCtrl(panel, wx.ID_ANY, self.nAuth[0], wx.DefaultPosition, wx.DefaultSize,
0)
0)
self.stPSetPassword = wx.StaticText(panel, wx.ID_ANY, "Password:", wx.DefaultPosition, wx.DefaultSize, 0)
self.stPSetPassword.Wrap(-1)
self.editProxySettingsPassword = wx.TextCtrl(panel, wx.ID_ANY, self.nAuth[1], wx.DefaultPosition,
wx.DefaultSize, wx.TE_PASSWORD)
wx.DefaultSize, wx.TE_PASSWORD)
pAuthSizer = wx.BoxSizer(wx.HORIZONTAL)
pAuthSizer.Add(self.stPSetLogin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
pAuthSizer.Add(self.editProxySettingsLogin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
@@ -134,7 +134,7 @@ class PFNetworkPref(PreferenceView):
mainSizer.Add(pAuthSizer, 0, wx.EXPAND, 5)
self.stPSAutoDetected = wx.StaticText(panel, wx.ID_ANY, "Auto-detected: ", wx.DefaultPosition, wx.DefaultSize,
0)
0)
self.stPSAutoDetected.Wrap(-1)
mainSizer.Add(self.stPSAutoDetected, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5)
@@ -150,10 +150,10 @@ class PFNetworkPref(PreferenceView):
proxy = self.settings.autodetect()
if proxy is not None:
addr, port = proxy
txt = addr + ":" + str(port)
addr, port = proxy
txt = addr + ":" + str(port)
else:
txt = "None"
txt = "None"
self.stPSAutoDetected.SetLabel("Auto-detected: " + txt)
self.stPSAutoDetected.Disable()

View File

@@ -289,6 +289,7 @@ class FitItem(SFItem.SFBrowserItem):
def editLostFocus(self, event):
self.RestoreEditButton()
self.Refresh()
event.Skip()
def editCheckEsc(self, event):
if event.GetKeyCode() == wx.WXK_ESCAPE:

View File

@@ -51,9 +51,13 @@ class PFBitmapFrame(wx.Frame):
pass
def OnWindowPaint(self, event):
# todo: evaluate wx.DragImage, might make this class obsolete, however might also lose our customizations
# (like the sexy fade-in animation)
rect = self.GetRect()
canvas = wx.Bitmap(rect.width, rect.height)
mdc = wx.AutoBufferedPaintDC(self)
# todo: convert to context manager after updating to wxPython >v4.0.1 (4.0.1 has a bug, see #1421)
# See #1418 for discussion
mdc = wx.BufferedPaintDC(self)
mdc.SelectObject(canvas)
mdc.DrawBitmap(self.bitmap, 0, 0)
mdc.SetPen(wx.Pen("#000000", width=1))

View File

@@ -72,6 +72,10 @@ class Miscellanea(ViewColumn):
if itemGroup == "Ship Modifiers":
return "", None
elif itemGroup == "Booster":
stuff.getModifiedItemAttr("boosterDuration")
text = "{0} min".format(formatAmount(stuff.getModifiedItemAttr("boosterDuration") / 1000 / 60, 3, 0, 3))
return text, "Booster Duration"
elif itemGroup in ("Energy Weapon", "Hybrid Weapon", "Projectile Weapon", "Combat Drone", "Fighter Drone"):
trackingSpeed = stuff.getModifiedItemAttr("trackingSpeed")
if not trackingSpeed:

View File

@@ -69,11 +69,12 @@ class FitSpawner(gui.multiSwitch.TabSpawner):
pyfalog.critical(e)
if count < 0:
startup = getattr(event, "startup", False) # see OpenFitsThread in gui.mainFrame
from_import = getattr(event, "from_import", False) # always open imported into a new tab
sFit = Fit.getInstance()
openFitInNew = sFit.serviceFittingOptions["openFitInNew"]
mstate = wx.GetMouseState()
if (not openFitInNew and mstate.CmdDown()) or startup or (openFitInNew and not mstate.CmdDown()):
if from_import or (not openFitInNew and mstate.CmdDown()) or startup or (openFitInNew and not mstate.CmdDown()):
self.multiSwitch.AddPage()
view = self.multiSwitch.GetSelectedPage()
@@ -286,6 +287,7 @@ class FittingView(d.Display):
If fit is removed and active, the page is deleted.
We also refresh the fit of the new current page in case
delete fit caused change in stats (projected)
todo: move this to the notebook, not the page. We don't want the page being responsible for deleting itself
"""
print('_+_+_+_+_+_ Fit Removed: {} {} activeFitID: {}, eventFitID: {}'.format(repr(self), str(bool(self)), self.activeFitID, event.fitID))
pyfalog.debug("FittingView::fitRemoved")
@@ -692,28 +694,26 @@ class FittingView(d.Display):
# pyfalog.critical(e)
def OnShow(self, event):
pass
# if event.Show():
# try:
# self.MakeSnapshot()
# except Exception as e:
# pyfalog.critical("Failed to make snapshot")
# pyfalog.critical(e)
# event.Skip()
if self and not self.IsShown():
try:
self.MakeSnapshot()
except Exception as e:
pyfalog.critical("Failed to make snapshot")
pyfalog.critical(e)
event.Skip()
def Snapshot(self):
return self.FVsnapshot
# noinspection PyPropertyAccess
def MakeSnapshot(self, maxColumns=1337):
if self.FVsnapshot:
del self.FVsnapshot
tbmp = wx.Bitmap(16, 16)
tdc = wx.MemoryDC()
tdc.SelectObject(tbmp)
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
tdc.SetFont(font)
columnsWidths = []
@@ -726,6 +726,7 @@ class FittingView(d.Display):
except Exception as e:
pyfalog.critical("Failed to get fit")
pyfalog.critical(e)
return
if fit is None:
return

View File

@@ -30,7 +30,7 @@ from gui.bitmap_loader import BitmapLoader
from gui.contextMenu import ContextMenu
import gui.globalEvents as GE
from gui.builtinViews.implantEditor import BaseImplantEditorView
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator, TextEntryValidatedDialog
from service.fit import Fit
from service.character import Character
from service.network import AuthenticationError, TimeoutError
@@ -42,9 +42,21 @@ from wx.lib.agw.floatspin import FloatSpin
from gui.utils.clipboard import toClipboard, fromClipboard
import roman
import re
pyfalog = Logger(__name__)
def arabicOrRomanToInt(s):
m = re.match(r'\d+$', s)
if m:
i = int(s)
else:
i = roman.fromRoman(s)
return i
class CharacterTextValidor(BaseValidator):
def __init__(self):
BaseValidator.__init__(self)
@@ -53,14 +65,14 @@ class CharacterTextValidor(BaseValidator):
return CharacterTextValidor()
def Validate(self, win):
entityEditor = win.parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
sChar = Character.getInstance()
try:
if len(text) == 0:
raise ValueError("You must supply a name for the Character!")
elif text in [x.name for x in entityEditor.choices]:
elif text in [x.name for x in sChar.getCharacterList()]:
raise ValueError("Character name already in use, please choose another.")
return True
@@ -230,8 +242,8 @@ class CharacterEditor(wx.Frame):
def saveCharAs(self, event):
char = self.entityEditor.getActiveEntity()
dlg = SaveCharacterAs(self, char.ID)
dlg.ShowModal()
self.SaveCharacterAs(self, char.ID)
wx.PostEvent(self, GE.CharListUpdated())
def revertChar(self, event):
sChr = Character.getInstance()
@@ -273,6 +285,22 @@ class CharacterEditor(wx.Frame):
wx.Frame.Destroy(self)
@staticmethod
def SaveCharacterAs(parent, charID):
sChar = Character.getInstance()
name = sChar.getCharName(charID)
dlg = TextEntryValidatedDialog(parent, CharacterTextValidor,
"Enter a name for your new Character:",
"Save Character As...")
dlg.SetValue("{} Copy".format(name))
dlg.txtctrl.SetInsertionPointEnd()
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
sChar = Character.getInstance()
return sChar.saveCharacterAs(charID, dlg.txtctrl.GetValue().strip())
class SkillTreeView(wx.Panel):
def __init__(self, parent):
@@ -319,8 +347,8 @@ class SkillTreeView(wx.Panel):
self.imageList = wx.ImageList(16, 16)
tree.SetImageList(self.imageList)
self.skillBookImageId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui"))
self.skillBookDirtyImageId = self.imageList.Add(BitmapLoader.getBitmap("skill_small_red", "gui"))
self.skillBookImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small", "gui")))
self.skillBookDirtyImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small_red", "gui")))
tree.AppendColumn("Skill")
tree.AppendColumn("Level")
@@ -414,7 +442,8 @@ class SkillTreeView(wx.Panel):
lines = text.splitlines()
for l in lines:
skill, level = l.strip()[:-1].strip(), int(l.strip()[-1])
s = l.strip()
skill, level = s.rsplit(None, 1)[0], arabicOrRomanToInt(s.rsplit(None, 1)[1])
skill = char.getSkill(skill)
if skill:
skill.setLevel(level, ignoreRestrict=True)
@@ -868,36 +897,6 @@ class APIView(wx.Panel):
self.stStatus.SetLabel("Unable to retrieve {}\'s skills. Error message:\n{}".format(charName, exc_obj))
class SaveCharacterAs(wx.Dialog):
def __init__(self, parent, charID):
wx.Dialog.__init__(self, parent, title="Save Character As...", size=wx.Size(300, 60))
self.charID = charID
self.parent = parent
sChar = Character.getInstance()
name = sChar.getCharName(charID)
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
self.input = wx.TextCtrl(self, wx.ID_ANY, name, style=wx.TE_PROCESS_ENTER)
bSizer1.Add(self.input, 1, wx.ALL, 5)
self.input.Bind(wx.EVT_TEXT_ENTER, self.change)
self.button = wx.Button(self, wx.ID_OK, "Save")
bSizer1.Add(self.button, 0, wx.ALL, 5)
self.SetSizer(bSizer1)
self.Layout()
self.Centre(wx.BOTH)
self.button.Bind(wx.EVT_BUTTON, self.change)
def change(self, event):
sChar = Character.getInstance()
sChar.saveCharacterAs(self.charID, self.input.GetLineText(0))
wx.PostEvent(self.parent, GE.CharListUpdated())
event.Skip()
self.Close()
class SecStatusDialog(wx.Dialog):
def __init__(self, parent, sec):

View File

@@ -97,7 +97,7 @@ class CharacterSelection(wx.Panel):
grantItem = menu.Append(wx.ID_ANY, "Grant Missing Skills")
self.Bind(wx.EVT_MENU, self.grantMissingSkills, grantItem)
exportItem = menu.Append(wx.ID_ANY, "Export Missing Skills")
exportItem = menu.Append(wx.ID_ANY, "Copy Missing Skills")
self.Bind(wx.EVT_MENU, self.exportSkills, exportItem)
self.PopupMenu(menu, pos)

View File

@@ -19,7 +19,7 @@ import wx.lib.newevent
from gui.bitmap_loader import BitmapLoader
from gui.utils import draw
from gui.utils import color as color_utils
from service.fit import Fit
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
@@ -1057,6 +1057,10 @@ class _TabsContainer(wx.Panel):
Checks to see if we have a tab preview and sets up the timer for it
to display
"""
sFit = Fit.getInstance()
if not sFit.serviceFittingOptions["showTooltip"] or False:
return
if self.preview_timer:
if self.preview_timer.IsRunning():
if self.preview_wnd:
@@ -1286,6 +1290,7 @@ class _TabsContainer(wx.Panel):
if not self.preview_tab.GetSelected():
page = self.Parent.GetPage(self.GetTabIndex(self.preview_tab))
if page.Snapshot():
self.preview_wnd = PFNotebookPagePreview(
self,
(mposx + 3, mposy + 3),
@@ -1373,7 +1378,7 @@ class PFNotebookPagePreview(wx.Frame):
def OnWindowPaint(self, event):
rect = self.GetRect()
canvas = wx.Bitmap(rect.width, rect.height)
mdc = wx.AudoBufferedPaintDC(self)
mdc = wx.BufferedPaintDC(self)
mdc.SelectObject(canvas)
color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
mdc.SetBackground(wx.Brush(color))

View File

@@ -170,8 +170,8 @@ class GraphFrame(wx.Frame):
self.AppendFitToList(fitID)
def close(self, event):
self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK)
self.mainFrame.Unbind(GE.FIT_CHANGED)
self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.removeItem)
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.draw)
event.Skip()
def getView(self):
@@ -229,6 +229,15 @@ class GraphFrame(wx.Frame):
def draw(self, event=None):
global mpl_version
if event is not None:
event.Skip()
# todo: FIX THIS, see #1430. draw() is not being unbound properly when the window closes, this is an easy fix,
# but not a proper solution
if not self:
pyfalog.warning("GraphFrame handled event, however GraphFrame no longer exists. Ignoring event")
return
values = self.getValues()
view = self.getView()
self.subplot.clear()
@@ -247,7 +256,7 @@ class GraphFrame(wx.Frame):
self.subplot.plot(x, y)
legend.append(fit.name)
except:
except Exception as ex:
pyfalog.warning("Invalid values in '{0}'", fit.name)
self.SetStatusText("Invalid values in '%s'" % fit.name)
self.canvas.draw()
@@ -299,8 +308,6 @@ class GraphFrame(wx.Frame):
self.canvas.draw()
self.SetStatusText("")
if event is not None:
event.Skip()
def onFieldChanged(self, event):
self.draw()

View File

@@ -49,7 +49,7 @@ from gui.multiSwitch import MultiSwitch
from gui.statsPane import StatsPane
from gui.shipBrowser import ShipBrowser
from gui.builtinShipBrowser.events import FitSelected, ImportSelected, Stage3Selected
from gui.characterEditor import CharacterEditor, SaveCharacterAs
from gui.characterEditor import CharacterEditor
from gui.characterSelection import CharacterSelection
from gui.patternEditor import DmgPatternEditorDlg
from gui.resistsEditor import ResistsEditorDlg
@@ -358,7 +358,7 @@ class MainFrame(wx.Frame):
def ShowAboutBox(self, evt):
info = wx.adv.AboutDialogInfo()
info.Name = "pyfa"
info.Version = config.getGitVersion() # gui.aboutData.versionString
info.Version = config.getVersion() # gui.aboutData.versionString
#
# try:
# import matplotlib
@@ -683,8 +683,8 @@ class MainFrame(wx.Frame):
def saveCharAs(self, event):
charID = self.charSelection.getActiveCharacter()
dlg = SaveCharacterAs(self, charID)
dlg.ShowModal()
CharacterEditor.SaveCharacterAs(self, charID)
wx.PostEvent(self, GE.CharListUpdated())
def revertChar(self, event):
sChr = Character.getInstance()
@@ -960,7 +960,7 @@ class MainFrame(wx.Frame):
if len(fits) > 0:
if len(fits) == 1:
fit = fits[0]
wx.PostEvent(self, FitSelected(fitID=fit.ID))
wx.PostEvent(self, FitSelected(fitID=fit.ID, from_import=True))
wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=fit.shipID, back=True))
else:
fits.sort(key=lambda _fit: (_fit.ship.item.name, _fit.name))

View File

@@ -38,7 +38,7 @@ class PreferenceDialog(wx.Dialog):
# self.listview.SetSize((500, -1))
self.imageList = wx.ImageList(32, 32)
self.listbook.SetImageList(self.imageList)
self.listbook.AssignImageList(self.imageList)
mainSizer.Add(self.listbook, 1, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.LEFT, 5)
@@ -55,17 +55,17 @@ class PreferenceDialog(wx.Dialog):
self.Centre(wx.BOTH)
for prefView in PreferenceView.views:
page = wx.Panel(self.listbook)
page = wx.ScrolledWindow(self.listbook)
page.SetScrollbars(1, 1, 20, 20)
bmp = prefView.getImage()
if bmp:
imgID = self.imageList.Add(bmp)
else:
imgID = -1
prefView.populatePanel(page)
self.listbook.AddPage(page, prefView.title, imageId=imgID)
# Set the height based on a condition. Can all the panels fit in the current height?
# If not, use the .GetBestVirtualSize() to ensure that all content is available.
minHeight = 550
bestFit = self.GetBestVirtualSize()
if minHeight > bestFit[1]:

View File

@@ -102,9 +102,11 @@ class AttributeEditor(wx.Frame):
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
with open(path, 'rb') as csvfile:
with open(path, 'r') as csvfile:
spamreader = csv.reader(csvfile)
for row in spamreader:
if len(row) == 0: # csvwriter seems to added blank lines to the end sometimes
continue
itemID, attrID, value = row
item = getItem(int(itemID))
attr = getAttributeInfo(int(attrID))
@@ -123,7 +125,7 @@ class AttributeEditor(wx.Frame):
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
with open(path, 'wb') as csvfile:
with open(path, 'w', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
for item in items:
for key, override in item.overrides.items():

View File

@@ -18,6 +18,7 @@ import wx
from gui.utils import color as color_utils
from gui.utils import draw, anim_effects
from service.fit import Fit
class PyGauge(wx.Window):
@@ -129,11 +130,16 @@ class PyGauge(wx.Window):
return self._max_range
def Animate(self):
if not self._timer:
self._timer = wx.Timer(self, self._timer_id)
sFit = Fit.getInstance()
if sFit.serviceFittingOptions["enableGaugeAnimation"]:
if not self._timer:
self._timer = wx.Timer(self, self._timer_id)
self._anim_step = 0
self._timer.Start(self._period)
self._anim_step = 0
self._timer.Start(self._period)
else:
self._anim_value = self._percentage
self.Refresh()
def SetRange(self, range, reinit=False, animate=True):
"""

View File

@@ -62,6 +62,7 @@ class UpdateDialog(wx.Dialog):
self.browser.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.OnNewWindow)
link_patterns = [
(re.compile("([0-9a-f]{6,40})", re.I), r"https://github.com/pyfa-org/Pyfa/commit/\1"),
(re.compile("#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"),
(re.compile("@(\w+)", re.I), r"https://github.com/\1")
]

View File

@@ -63,12 +63,14 @@ class exportHtmlThread(threading.Thread):
HTML = self.generateFullHTML(sMkt, sFit, dnaUrl)
try:
FILE = open(settings.getPath(), "w")
FILE.write(HTML.encode('utf-8'))
FILE = open(settings.getPath(), "w", encoding='utf-8')
FILE.write(HTML)
FILE.close()
except IOError:
except IOError as ex:
print(("Failed to write to " + settings.getPath()))
pass
except Exception as ex:
pass
if self.callback:
wx.CallAfter(self.callback, -1)
@@ -84,6 +86,7 @@ class exportHtmlThread(threading.Thread):
<head>
<title>Pyfa Fittings</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8" />
<link rel="stylesheet" href="https://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css" />
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<script>

BIN
imgs/icons/101_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

View File

@@ -95,10 +95,6 @@ if __name__ == "__main__":
if options.rootsavedata is True:
config.saveInRoot = True
# set title if it wasn't supplied by argument
if options.title is None:
options.title = "pyfa %s%s - Python Fitting Assistant" % (config.version, "" if config.tag.lower() != 'git' else " (git)")
config.debug = options.debug
config.loggingLevel = config.LOGLEVEL_MAP.get(options.logginglevel.lower(), config.LOGLEVEL_MAP['error'])
config.defPaths(options.savepath)
@@ -131,6 +127,10 @@ if __name__ == "__main__":
from gui.mainFrame import MainFrame
# set title if it wasn't supplied by argument
if options.title is None:
options.title = "pyfa %s - Python Fitting Assistant" % (config.getVersion())
pyfa = wx.App(False)
mf = MainFrame(options.title)
ErrorHandler.SetParent(mf)

View File

@@ -1,4 +1,4 @@
wxPython >= 4.0.1
wxPython == 4.0.0b2
logbook >= 1.0.0
matplotlib >= 2.0.0
python-dateutil
@@ -6,4 +6,6 @@ requests >= 2.0.0
sqlalchemy >= 1.0.5
esipy == 0.3.0
markdown2
packaging
packaging
roman
beautifulsoup4

View File

@@ -208,7 +208,7 @@ def main(old, new, groups=True, effects=True, attributes=True, renames=True):
for cursor, dictionary in ((old_cursor, old_itmdata), (new_cursor, new_itmdata)):
# Compose list of items we're interested in, filtered by category
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID INNER JOIN invcategories AS ic ON ig.categoryID = ic.categoryID WHERE it.published = 1 AND ic.categoryName IN ("Ship", "Module", "Charge", "Skill", "Drone", "Implant", "Subsystem")'
query = 'SELECT it.typeID, it.groupID FROM invtypes AS it INNER JOIN invgroups AS ig ON it.groupID = ig.groupID INNER JOIN invcategories AS ic ON ig.categoryID = ic.categoryID WHERE it.published = 1 AND ic.categoryName IN ("Ship", "Module", "Charge", "Skill", "Drone", "Implant", "Subsystem", "Structure", "Structure Module", "Fighter")'
cursor.execute(query)
for row in cursor:
itemid = row[0]

View File

@@ -108,6 +108,7 @@ class SkillBackupThread(threading.Thread):
def run(self):
path = self.path
sCharacter = Character.getInstance()
if self.saveFmt == "xml" or self.saveFmt == "emp":
backupData = sCharacter.exportXml()
else:
@@ -115,14 +116,13 @@ class SkillBackupThread(threading.Thread):
if self.saveFmt == "emp":
with gzip.open(path, mode='wb') as backupFile:
backupFile.write(backupData)
backupFile.write(backupData.encode())
else:
with open(path, mode='w', encoding='utf-8') as backupFile:
backupFile.write(backupData)
wx.CallAfter(self.callback)
class Character(object):
instance = None
skillReqsDict = {}
@@ -246,6 +246,7 @@ class Character(object):
# revert old char
char.revertLevels()
return newChar.ID
@staticmethod
def revertCharacter(charID):

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