diff --git a/.gitignore b/.gitignore index 6cf50c952..c5efd554c 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,6 @@ ENV/ eos.iml gitversion .version +/.version +*.swp + diff --git a/.version b/.version deleted file mode 100644 index 59d5e729e..000000000 --- a/.version +++ /dev/null @@ -1 +0,0 @@ -v1.2.3-221-g50dd74db \ No newline at end of file diff --git a/config.py b/config.py index fb834f33e..35ab81c76 100644 --- a/config.py +++ b/config.py @@ -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 diff --git a/dist_assets/win/pyfa.spec b/dist_assets/win/pyfa.spec index f3522bd81..598fe546b 100644 --- a/dist_assets/win/pyfa.spec +++ b/dist_assets/win/pyfa.spec @@ -62,7 +62,7 @@ exe = EXE(pyz, a.scripts, exclude_binaries=True, debug=False, - console=True, + console=False, strip=False, upx=True, name='pyfa', diff --git a/dist_assets/win/version_resource.py b/dist_assets/win/version_resource.py new file mode 100644 index 000000000..a5018edd7 --- /dev/null +++ b/dist_assets/win/version_resource.py @@ -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])]) + ] +) diff --git a/eos/config.py b/eos/config.py index 2c1dd7e71..6bc31eef8 100644 --- a/eos/config.py +++ b/eos/config.py @@ -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) diff --git a/eos/db/saveddata/loadDefaultDatabaseValues.py b/eos/db/saveddata/loadDefaultDatabaseValues.py index befaad44b..51f497cc6 100644 --- a/eos/db/saveddata/loadDefaultDatabaseValues.py +++ b/eos/db/saveddata/loadDefaultDatabaseValues.py @@ -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"], diff --git a/eos/effects/accessdifficultybonusmodifierrequiringhacking.py b/eos/effects/accessdifficultybonusmodifierrequiringhacking.py index 6bb5ffdd7..2094f08d3 100644 --- a/eos/effects/accessdifficultybonusmodifierrequiringhacking.py +++ b/eos/effects/accessdifficultybonusmodifierrequiringhacking.py @@ -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" diff --git a/eos/effects/ammoinfluencecapneed.py b/eos/effects/ammoinfluencecapneed.py index 6a2a2854f..d5110c5f1 100644 --- a/eos/effects/ammoinfluencecapneed.py +++ b/eos/effects/ammoinfluencecapneed.py @@ -1,7 +1,7 @@ # ammoInfluenceCapNeed # # Used by: -# Items from category: Charge (478 of 924) +# Items from category: Charge (478 of 925) type = "passive" diff --git a/eos/effects/ammoinfluencerange.py b/eos/effects/ammoinfluencerange.py index 43abc89ab..1c7d4ea44 100644 --- a/eos/effects/ammoinfluencerange.py +++ b/eos/effects/ammoinfluencerange.py @@ -1,7 +1,7 @@ # ammoInfluenceRange # # Used by: -# Items from category: Charge (572 of 924) +# Items from category: Charge (572 of 925) type = "passive" diff --git a/eos/effects/ammospeedmultiplier.py b/eos/effects/ammospeedmultiplier.py index e375d6738..31ebe295b 100644 --- a/eos/effects/ammospeedmultiplier.py +++ b/eos/effects/ammospeedmultiplier.py @@ -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" diff --git a/eos/effects/citadelrigbonus.py b/eos/effects/citadelrigbonus.py new file mode 100644 index 000000000..a04c6f479 --- /dev/null +++ b/eos/effects/citadelrigbonus.py @@ -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")) diff --git a/eos/effects/damagecontrol.py b/eos/effects/damagecontrol.py index e047e6afa..05589e15a 100644 --- a/eos/effects/damagecontrol.py +++ b/eos/effects/damagecontrol.py @@ -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" diff --git a/eos/effects/dohacking.py b/eos/effects/dohacking.py index cf029e12d..233d29404 100644 --- a/eos/effects/dohacking.py +++ b/eos/effects/dohacking.py @@ -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" diff --git a/eos/effects/doomsdayaoebubble.py b/eos/effects/doomsdayaoebubble.py new file mode 100644 index 000000000..c4945d697 --- /dev/null +++ b/eos/effects/doomsdayaoebubble.py @@ -0,0 +1,9 @@ +# doomsdayAOEBubble +# +# Used by: +# Module: Standup Warp Disruption Burst Projector +type = "projected", "active" + + +def handler(fit, module, context): + return diff --git a/eos/effects/doomsdayaoedamp.py b/eos/effects/doomsdayaoedamp.py new file mode 100644 index 000000000..f943c9153 --- /dev/null +++ b/eos/effects/doomsdayaoedamp.py @@ -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) diff --git a/eos/effects/doomsdayaoeneut.py b/eos/effects/doomsdayaoeneut.py new file mode 100644 index 000000000..c132a470a --- /dev/null +++ b/eos/effects/doomsdayaoeneut.py @@ -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) diff --git a/eos/effects/doomsdayaoepaint.py b/eos/effects/doomsdayaoepaint.py new file mode 100644 index 000000000..b02f68e38 --- /dev/null +++ b/eos/effects/doomsdayaoepaint.py @@ -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) diff --git a/eos/effects/doomsdayaoetrack.py b/eos/effects/doomsdayaoetrack.py new file mode 100644 index 000000000..910ec7ae5 --- /dev/null +++ b/eos/effects/doomsdayaoetrack.py @@ -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) diff --git a/eos/effects/doomsdayaoeweb.py b/eos/effects/doomsdayaoeweb.py new file mode 100644 index 000000000..913288b93 --- /dev/null +++ b/eos/effects/doomsdayaoeweb.py @@ -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) diff --git a/eos/effects/elitebonusgunshipdronecapacity2.py b/eos/effects/elitebonusgunshipdronecapacity2.py index 47dc4249b..61cf46c51 100644 --- a/eos/effects/elitebonusgunshipdronecapacity2.py +++ b/eos/effects/elitebonusgunshipdronecapacity2.py @@ -1,7 +1,4 @@ -# eliteBonusGunshipDroneCapacity2 -# -# Used by: -# Ship: Ishkur +# Not used by any item type = "passive" diff --git a/eos/effects/elitebonusgunshipdronetracking2.py b/eos/effects/elitebonusgunshipdronetracking2.py new file mode 100644 index 000000000..10d8c753d --- /dev/null +++ b/eos/effects/elitebonusgunshipdronetracking2.py @@ -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") diff --git a/eos/effects/elitebonusgunshipemmissiledamage1.py b/eos/effects/elitebonusgunshipemmissiledamage1.py new file mode 100644 index 000000000..ae5a4aed2 --- /dev/null +++ b/eos/effects/elitebonusgunshipemmissiledamage1.py @@ -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") diff --git a/eos/effects/elitebonusgunshipexplosionvelocity2.py b/eos/effects/elitebonusgunshipexplosionvelocity2.py new file mode 100644 index 000000000..e17de01c1 --- /dev/null +++ b/eos/effects/elitebonusgunshipexplosionvelocity2.py @@ -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") diff --git a/eos/effects/elitebonusgunshipexplosivemissiledamage1.py b/eos/effects/elitebonusgunshipexplosivemissiledamage1.py new file mode 100644 index 000000000..626f6ce51 --- /dev/null +++ b/eos/effects/elitebonusgunshipexplosivemissiledamage1.py @@ -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") diff --git a/eos/effects/elitebonusgunshipkineticmissiledamage1.py b/eos/effects/elitebonusgunshipkineticmissiledamage1.py new file mode 100644 index 000000000..18afde89e --- /dev/null +++ b/eos/effects/elitebonusgunshipkineticmissiledamage1.py @@ -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") diff --git a/eos/effects/elitebonusgunshipprojectiledamage2.py b/eos/effects/elitebonusgunshipprojectiledamage2.py index 09f2df3ad..81ad55805 100644 --- a/eos/effects/elitebonusgunshipprojectiledamage2.py +++ b/eos/effects/elitebonusgunshipprojectiledamage2.py @@ -1,7 +1,4 @@ -# eliteBonusGunshipProjectileDamage2 -# -# Used by: -# Ship: Jaguar +# Not used by any item type = "passive" diff --git a/eos/effects/elitebonusgunshipprojectileoptimal1.py b/eos/effects/elitebonusgunshipprojectileoptimal1.py index b1df7d247..75327d04f 100644 --- a/eos/effects/elitebonusgunshipprojectileoptimal1.py +++ b/eos/effects/elitebonusgunshipprojectileoptimal1.py @@ -1,7 +1,4 @@ -# eliteBonusGunshipProjectileOptimal1 -# -# Used by: -# Ship: Jaguar +# Not used by any item type = "passive" diff --git a/eos/effects/elitebonusgunshipthermalmissiledamage1.py b/eos/effects/elitebonusgunshipthermalmissiledamage1.py new file mode 100644 index 000000000..04ee9c25d --- /dev/null +++ b/eos/effects/elitebonusgunshipthermalmissiledamage1.py @@ -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") diff --git a/eos/effects/fighterabilitystasiswebifier.py b/eos/effects/fighterabilitystasiswebifier.py index 0aa3b5faf..1fd6dd96f 100644 --- a/eos/effects/fighterabilitystasiswebifier.py +++ b/eos/effects/fighterabilitystasiswebifier.py @@ -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) diff --git a/eos/effects/hackingskillvirusbonus.py b/eos/effects/hackingskillvirusbonus.py index 35c3be199..45610c4f9 100644 --- a/eos/effects/hackingskillvirusbonus.py +++ b/eos/effects/hackingskillvirusbonus.py @@ -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 diff --git a/eos/effects/hackingvirusstrengthbonus.py b/eos/effects/hackingvirusstrengthbonus.py index d830a228e..6307a9224 100644 --- a/eos/effects/hackingvirusstrengthbonus.py +++ b/eos/effects/hackingvirusstrengthbonus.py @@ -1,4 +1,7 @@ -# Not used by any item +# hackingVirusStrengthBonus +# +# Used by: +# Implant: Neural Lace 'Blackglass' Net Intrusion 920-40 type = "passive" diff --git a/eos/effects/modulebonusassaultdamagecontrol.py b/eos/effects/modulebonusassaultdamagecontrol.py new file mode 100644 index 000000000..a7a6d9551 --- /dev/null +++ b/eos/effects/modulebonusassaultdamagecontrol.py @@ -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")) diff --git a/eos/effects/remotewebifiermaxrangebonus.py b/eos/effects/remotewebifiermaxrangebonus.py new file mode 100644 index 000000000..19498882a --- /dev/null +++ b/eos/effects/remotewebifiermaxrangebonus.py @@ -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) diff --git a/eos/effects/scriptscangravimetricstrengthbonusbonus.py b/eos/effects/scriptscangravimetricstrengthbonusbonus.py new file mode 100644 index 000000000..e9003c77d --- /dev/null +++ b/eos/effects/scriptscangravimetricstrengthbonusbonus.py @@ -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")) diff --git a/eos/effects/scriptscanladarstrengthbonusbonus.py b/eos/effects/scriptscanladarstrengthbonusbonus.py new file mode 100644 index 000000000..9300e4cca --- /dev/null +++ b/eos/effects/scriptscanladarstrengthbonusbonus.py @@ -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")) diff --git a/eos/effects/scriptscanmagnetometricstrengthbonusbonus.py b/eos/effects/scriptscanmagnetometricstrengthbonusbonus.py new file mode 100644 index 000000000..99e18d3c3 --- /dev/null +++ b/eos/effects/scriptscanmagnetometricstrengthbonusbonus.py @@ -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")) diff --git a/eos/effects/scriptscanradarstrengthbonusbonus.py b/eos/effects/scriptscanradarstrengthbonusbonus.py new file mode 100644 index 000000000..d747d6510 --- /dev/null +++ b/eos/effects/scriptscanradarstrengthbonusbonus.py @@ -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")) diff --git a/eos/effects/scriptstandupwarpscram.py b/eos/effects/scriptstandupwarpscram.py new file mode 100644 index 000000000..6f03786d6 --- /dev/null +++ b/eos/effects/scriptstandupwarpscram.py @@ -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")) diff --git a/eos/effects/servicemodulefullpowerhitpointpostassign.py b/eos/effects/servicemodulefullpowerhitpointpostassign.py new file mode 100644 index 000000000..edcb40ae1 --- /dev/null +++ b/eos/effects/servicemodulefullpowerhitpointpostassign.py @@ -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")) diff --git a/eos/effects/shipbonusdronetrackingelitegunship2.py b/eos/effects/shipbonusdronetrackingelitegunship2.py new file mode 100644 index 000000000..20c82238a --- /dev/null +++ b/eos/effects/shipbonusdronetrackingelitegunship2.py @@ -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") diff --git a/eos/effects/shipmissilerofmf2.py b/eos/effects/shipmissilerofmf2.py index 140e856db..4661fbef0 100644 --- a/eos/effects/shipmissilerofmf2.py +++ b/eos/effects/shipmissilerofmf2.py @@ -2,6 +2,7 @@ # # Used by: # Ship: Breacher +# Ship: Jaguar type = "passive" diff --git a/eos/effects/shippdmgbonusmf.py b/eos/effects/shippdmgbonusmf.py index 4fa2ee88e..0a359917e 100644 --- a/eos/effects/shippdmgbonusmf.py +++ b/eos/effects/shippdmgbonusmf.py @@ -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" diff --git a/eos/effects/shipprojectiletrackingmf2.py b/eos/effects/shipprojectiletrackingmf2.py index 88b8f6226..588b6c7ff 100644 --- a/eos/effects/shipprojectiletrackingmf2.py +++ b/eos/effects/shipprojectiletrackingmf2.py @@ -2,7 +2,6 @@ # # Used by: # Variations of ship: Slasher (3 of 3) -# Ship: Jaguar # Ship: Republic Fleet Firetail # Ship: Wolf type = "passive" diff --git a/eos/effects/shipsetrofaf.py b/eos/effects/shipsetrofaf.py new file mode 100644 index 000000000..df4394b19 --- /dev/null +++ b/eos/effects/shipsetrofaf.py @@ -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") diff --git a/eos/effects/shipsettrackingbonusaf.py b/eos/effects/shipsettrackingbonusaf.py index f14d24419..f8e952ffc 100644 --- a/eos/effects/shipsettrackingbonusaf.py +++ b/eos/effects/shipsettrackingbonusaf.py @@ -1,7 +1,4 @@ -# shipSETTrackingBonusAF -# -# Used by: -# Ship: Retribution +# Not used by any item type = "passive" diff --git a/eos/effects/shipshieldboostmf.py b/eos/effects/shipshieldboostmf.py index 1931f684d..ced5d2799 100644 --- a/eos/effects/shipshieldboostmf.py +++ b/eos/effects/shipshieldboostmf.py @@ -2,6 +2,7 @@ # # Used by: # Ship: Breacher +# Ship: Jaguar type = "passive" diff --git a/eos/effects/structureaoerofrolebonus.py b/eos/effects/structureaoerofrolebonus.py new file mode 100644 index 000000000..78b3205ab --- /dev/null +++ b/eos/effects/structureaoerofrolebonus.py @@ -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")) diff --git a/eos/effects/structurearmorhpmultiply.py b/eos/effects/structurearmorhpmultiply.py new file mode 100644 index 000000000..1e34232ed --- /dev/null +++ b/eos/effects/structurearmorhpmultiply.py @@ -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")) diff --git a/eos/effects/structureballisticcontrolsystem.py b/eos/effects/structureballisticcontrolsystem.py index e075af277..8a167b6be 100644 --- a/eos/effects/structureballisticcontrolsystem.py +++ b/eos/effects/structureballisticcontrolsystem.py @@ -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) diff --git a/eos/effects/structurecapacitorcapacitybonus.py b/eos/effects/structurecapacitorcapacitybonus.py new file mode 100644 index 000000000..df4384f5b --- /dev/null +++ b/eos/effects/structurecapacitorcapacitybonus.py @@ -0,0 +1,6 @@ +# Not used by any item +type = "passive" + + +def handler(fit, ship, context): + fit.ship.increaseItemAttr("capacitorCapacity", ship.getModifiedItemAttr("capacitorBonus")) diff --git a/eos/effects/structurefullpowerstatehitpointmodifier.py b/eos/effects/structurefullpowerstatehitpointmodifier.py new file mode 100644 index 000000000..54f9ce7b6 --- /dev/null +++ b/eos/effects/structurefullpowerstatehitpointmodifier.py @@ -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) diff --git a/eos/effects/structurehiddenarmorhpmultiplier.py b/eos/effects/structurehiddenarmorhpmultiplier.py new file mode 100644 index 000000000..09e2d31c7 --- /dev/null +++ b/eos/effects/structurehiddenarmorhpmultiplier.py @@ -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) diff --git a/eos/effects/structurehiddenmissiledamagemultiplier.py b/eos/effects/structurehiddenmissiledamagemultiplier.py new file mode 100644 index 000000000..bd8d79366 --- /dev/null +++ b/eos/effects/structurehiddenmissiledamagemultiplier.py @@ -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")) diff --git a/eos/effects/structuremissileguidanceenhancer.py b/eos/effects/structuremissileguidanceenhancer.py new file mode 100644 index 000000000..9c0ce2515 --- /dev/null +++ b/eos/effects/structuremissileguidanceenhancer.py @@ -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) diff --git a/eos/effects/structuremodifypowerrechargerate.py b/eos/effects/structuremodifypowerrechargerate.py new file mode 100644 index 000000000..ed8e9c074 --- /dev/null +++ b/eos/effects/structuremodifypowerrechargerate.py @@ -0,0 +1,6 @@ +# Not used by any item +type = "passive" + + +def handler(fit, module, context): + fit.ship.multiplyItemAttr("rechargeRate", module.getModifiedItemAttr("capacitorRechargeRateMultiplier")) diff --git a/eos/effects/structurerigmaxtargetrange.py b/eos/effects/structurerigmaxtargetrange.py new file mode 100644 index 000000000..95fee8a55 --- /dev/null +++ b/eos/effects/structurerigmaxtargetrange.py @@ -0,0 +1,6 @@ +# Not used by any item +type = "passive" + + +def handler(fit, module, context): + fit.ship.boostItemAttr("maxTargetRange", module.getModifiedItemAttr("structureRigMaxTargetRangeBonus")) diff --git a/eos/effects/structurewarpscrambleblockmwdwithnpceffect.py b/eos/effects/structurewarpscrambleblockmwdwithnpceffect.py index 283a1451f..f22439441 100644 --- a/eos/effects/structurewarpscrambleblockmwdwithnpceffect.py +++ b/eos/effects/structurewarpscrambleblockmwdwithnpceffect.py @@ -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 diff --git a/eos/effects/warpdisruptsphere.py b/eos/effects/warpdisruptsphere.py index 4a6d788e9..7cddfcb86 100644 --- a/eos/effects/warpdisruptsphere.py +++ b/eos/effects/warpdisruptsphere.py @@ -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) diff --git a/eos/effects/warpscrambleblockmwdwithnpceffect.py b/eos/effects/warpscrambleblockmwdwithnpceffect.py index d5144a4a2..2e075207e 100644 --- a/eos/effects/warpscrambleblockmwdwithnpceffect.py +++ b/eos/effects/warpscrambleblockmwdwithnpceffect.py @@ -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 diff --git a/eos/graph/fitDps.py b/eos/graph/fitDps.py index 58102f2b2..6aead3583 100644 --- a/eos/graph/fitDps.py +++ b/eos/graph/fitDps.py @@ -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"]: diff --git a/eos/saveddata/damagePattern.py b/eos/saveddata/damagePattern.py index 2d32de6a2..d7889f131 100644 --- a/eos/saveddata/damagePattern.py +++ b/eos/saveddata/damagePattern.py @@ -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" diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py index cf9309de2..da4c512f2 100644 --- a/eos/saveddata/drone.py +++ b/eos/saveddata/drone.py @@ -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: diff --git a/eos/saveddata/fighter.py b/eos/saveddata/fighter.py index a88189739..180838e31 100644 --- a/eos/saveddata/fighter.py +++ b/eos/saveddata/fighter.py @@ -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(): diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index c4b855973..e453bc7d0 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -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): diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index 8e77a539a..e57ad9110 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -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 diff --git a/eos/saveddata/targetResists.py b/eos/saveddata/targetResists.py index e50c13791..63b64afef 100644 --- a/eos/saveddata/targetResists.py +++ b/eos/saveddata/targetResists.py @@ -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" diff --git a/eve.db b/eve.db index 688a52300..90d9e3b6a 100644 Binary files a/eve.db and b/eve.db differ diff --git a/gui/builtinAdditionPanes/boosterView.py b/gui/builtinAdditionPanes/boosterView.py index c19a724d2..8923f83de 100644 --- a/gui/builtinAdditionPanes/boosterView.py +++ b/gui/builtinAdditionPanes/boosterView.py @@ -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) diff --git a/gui/builtinAdditionPanes/droneView.py b/gui/builtinAdditionPanes/droneView.py index 41327618b..e9b893047 100644 --- a/gui/builtinAdditionPanes/droneView.py +++ b/gui/builtinAdditionPanes/droneView.py @@ -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) diff --git a/gui/builtinAdditionPanes/fighterView.py b/gui/builtinAdditionPanes/fighterView.py index 9eec32dab..3292583fd 100644 --- a/gui/builtinAdditionPanes/fighterView.py +++ b/gui/builtinAdditionPanes/fighterView.py @@ -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( diff --git a/gui/builtinAdditionPanes/implantView.py b/gui/builtinAdditionPanes/implantView.py index 8e043702f..b1d7bbe66 100644 --- a/gui/builtinAdditionPanes/implantView.py +++ b/gui/builtinAdditionPanes/implantView.py @@ -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) diff --git a/gui/builtinAdditionPanes/projectedView.py b/gui/builtinAdditionPanes/projectedView.py index 58499c74e..a5654d1a4 100644 --- a/gui/builtinAdditionPanes/projectedView.py +++ b/gui/builtinAdditionPanes/projectedView.py @@ -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() diff --git a/gui/builtinContextMenus/amount.py b/gui/builtinContextMenus/amount.py index 86c6411fd..6484595eb 100644 --- a/gui/builtinContextMenus/amount.py +++ b/gui/builtinContextMenus/amount.py @@ -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 diff --git a/gui/builtinContextMenus/factorReload.py b/gui/builtinContextMenus/factorReload.py index 64e246413..5600c4c5c 100644 --- a/gui/builtinContextMenus/factorReload.py +++ b/gui/builtinContextMenus/factorReload.py @@ -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 diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py index c9850a6e5..9e18ce0ff 100644 --- a/gui/builtinItemStatsViews/itemAttributes.py +++ b/gui/builtinItemStatsViews/itemAttributes.py @@ -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) diff --git a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py index e83fcd799..6de36df1c 100644 --- a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py +++ b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py @@ -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) diff --git a/gui/builtinPreferenceViews/pyfaNetworkPreferences.py b/gui/builtinPreferenceViews/pyfaNetworkPreferences.py index 6b7669e15..4f9887f8a 100644 --- a/gui/builtinPreferenceViews/pyfaNetworkPreferences.py +++ b/gui/builtinPreferenceViews/pyfaNetworkPreferences.py @@ -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() diff --git a/gui/builtinShipBrowser/fitItem.py b/gui/builtinShipBrowser/fitItem.py index fbc0a3020..3d782fe5f 100644 --- a/gui/builtinShipBrowser/fitItem.py +++ b/gui/builtinShipBrowser/fitItem.py @@ -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: diff --git a/gui/builtinShipBrowser/pfBitmapFrame.py b/gui/builtinShipBrowser/pfBitmapFrame.py index 1b5631dac..85ad487d6 100644 --- a/gui/builtinShipBrowser/pfBitmapFrame.py +++ b/gui/builtinShipBrowser/pfBitmapFrame.py @@ -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)) diff --git a/gui/builtinViewColumns/misc.py b/gui/builtinViewColumns/misc.py index bb936d34f..5378482ad 100644 --- a/gui/builtinViewColumns/misc.py +++ b/gui/builtinViewColumns/misc.py @@ -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: diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index f1bb3f89d..362de074b 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -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 diff --git a/gui/characterEditor.py b/gui/characterEditor.py index f8b79ae8c..79602dbd7 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -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): diff --git a/gui/characterSelection.py b/gui/characterSelection.py index a75306436..532edb379 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -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) diff --git a/gui/chrome_tabs.py b/gui/chrome_tabs.py index 8aa85929b..c03edacab 100644 --- a/gui/chrome_tabs.py +++ b/gui/chrome_tabs.py @@ -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)) diff --git a/gui/graphFrame.py b/gui/graphFrame.py index 1ecf2daca..a36c9c66a 100644 --- a/gui/graphFrame.py +++ b/gui/graphFrame.py @@ -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() diff --git a/gui/mainFrame.py b/gui/mainFrame.py index a7c1ea7b7..c893449c0 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -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)) diff --git a/gui/preferenceDialog.py b/gui/preferenceDialog.py index 05701987d..37ca5ddaf 100644 --- a/gui/preferenceDialog.py +++ b/gui/preferenceDialog.py @@ -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]: diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 798e1453e..68cc93ee8 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -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(): diff --git a/gui/pyfa_gauge.py b/gui/pyfa_gauge.py index de0ce1ba8..0144f651c 100644 --- a/gui/pyfa_gauge.py +++ b/gui/pyfa_gauge.py @@ -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): """ diff --git a/gui/updateDialog.py b/gui/updateDialog.py index a38de401f..c575eeaf3 100644 --- a/gui/updateDialog.py +++ b/gui/updateDialog.py @@ -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") ] diff --git a/gui/utils/exportHtml.py b/gui/utils/exportHtml.py index 984505021..612df367e 100644 --- a/gui/utils/exportHtml.py +++ b/gui/utils/exportHtml.py @@ -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):