diff --git a/README.md b/README.md index a3b795749..5700287f8 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ If you wish to help with development or simply need to run pyfa through a Python * `dateutil` * `matplotlib` (for some Linux distributions you may need to install separate wxPython bindings such as `python-matplotlib-wx`) * `requests` +* `logbook` >= 1.0.0 ## Bug Reporting The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting). diff --git a/_development/__init__.py b/_development/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/_development/helpers.py b/_development/helpers.py new file mode 100644 index 000000000..b35ac835f --- /dev/null +++ b/_development/helpers.py @@ -0,0 +1,143 @@ +# noinspection PyPackageRequirements +import pytest + +import os +import sys +import threading + +from sqlalchemy import MetaData, create_engine +from sqlalchemy.orm import sessionmaker + +script_dir = os.path.dirname(os.path.abspath(__file__)) +# Add root folder to python paths +sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..'))) +sys._called_from_test = True + +# noinspection PyUnresolvedReferences,PyUnusedLocal +@pytest.fixture +def DBInMemory_test(): + def rollback(): + with sd_lock: + saveddata_session.rollback() + + + print("Creating database in memory") + from os.path import realpath, join, dirname, abspath + + debug = False + gamedataCache = True + saveddataCache = True + gamedata_version = "" + gamedata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(unicode(__file__))), "..", "eve.db")) + saveddata_connectionstring = 'sqlite:///:memory:' + + class ReadOnlyException(Exception): + pass + + if callable(gamedata_connectionstring): + gamedata_engine = create_engine("sqlite://", creator=gamedata_connectionstring, echo=debug) + else: + gamedata_engine = create_engine(gamedata_connectionstring, echo=debug) + + gamedata_meta = MetaData() + gamedata_meta.bind = gamedata_engine + gamedata_session = sessionmaker(bind=gamedata_engine, autoflush=False, expire_on_commit=False)() + + # This should be moved elsewhere, maybe as an actual query. Current, without try-except, it breaks when making a new + # game db because we haven't reached gamedata_meta.create_all() + try: + gamedata_version = gamedata_session.execute( + "SELECT `field_value` FROM `metadata` WHERE `field_name` LIKE 'client_build'" + ).fetchone()[0] + except Exception as e: + print("Missing gamedata version.") + gamedata_version = None + + if saveddata_connectionstring is not None: + if callable(saveddata_connectionstring): + saveddata_engine = create_engine(creator=saveddata_connectionstring, echo=debug) + else: + saveddata_engine = create_engine(saveddata_connectionstring, echo=debug) + + saveddata_meta = MetaData() + saveddata_meta.bind = saveddata_engine + saveddata_session = sessionmaker(bind=saveddata_engine, autoflush=False, expire_on_commit=False)() + else: + saveddata_meta = None + + # Lock controlling any changes introduced to session + sd_lock = threading.Lock() + + # Import all the definitions for all our database stuff + # noinspection PyPep8 + #from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit + # noinspection PyPep8 + #from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, miscData, module, override, price, queries, skill, targetResists, user + + # If using in memory saveddata, you'll want to reflect it so the data structure is good. + if saveddata_connectionstring == "sqlite:///:memory:": + saveddata_meta.create_all() + + # Output debug info to help us troubleshoot Travis + print(saveddata_engine) + print(gamedata_engine) + + helper = { + #'config': eos.config, + 'gamedata_session' : gamedata_session, + 'saveddata_session' : saveddata_session, + } + return helper + +# noinspection PyUnresolvedReferences,PyUnusedLocal +@pytest.fixture +def DBInMemory(): + print("Creating database in memory") + + import eos.config + + import eos + import eos.db + + # Output debug info to help us troubleshoot Travis + print(eos.db.saveddata_engine) + print(eos.db.gamedata_engine) + + helper = { + 'config': eos.config, + 'db' : eos.db, + 'gamedata_session' : eos.db.gamedata_session, + 'saveddata_session' : eos.db.saveddata_session, + } + return helper + + +@pytest.fixture +def Gamedata(): + print("Building Gamedata") + from eos.gamedata import Item + + helper = { + 'Item': Item, + } + return helper + + +@pytest.fixture +def Saveddata(): + print("Building Saveddata") + from eos.saveddata.ship import Ship + from eos.saveddata.fit import Fit + from eos.saveddata.character import Character + from eos.saveddata.module import Module, State + from eos.saveddata.citadel import Citadel + + helper = { + 'Structure': Citadel, + 'Ship' : Ship, + 'Fit' : Fit, + 'Character': Character, + 'Module' : Module, + 'State' : State, + } + return helper diff --git a/_development/helpers_fits.py b/_development/helpers_fits.py new file mode 100644 index 000000000..0e41b9f6e --- /dev/null +++ b/_development/helpers_fits.py @@ -0,0 +1,28 @@ +import pytest + +# noinspection PyPackageRequirements +from _development.helpers import DBInMemory as DB, Gamedata, Saveddata + + +# noinspection PyShadowingNames +@pytest.fixture +def RifterFit(DB, Gamedata, Saveddata): + print("Creating Rifter") + item = DB['gamedata_session'].query(Gamedata['Item']).filter(Gamedata['Item'].name == "Rifter").first() + ship = Saveddata['Ship'](item) + # setup fit + fit = Saveddata['Fit'](ship, "My Rifter Fit") + + return fit + + +# noinspection PyShadowingNames +@pytest.fixture +def KeepstarFit(DB, Gamedata, Saveddata): + print("Creating Keepstar") + item = DB['gamedata_session'].query(Gamedata['Item']).filter(Gamedata['Item'].name == "Keepstar").first() + ship = Saveddata['Structure'](item) + # setup fit + fit = Saveddata['Fit'](ship, "Keepstar Fit") + + return fit diff --git a/config.py b/config.py index 7c807e3be..1b84f837a 100644 --- a/config.py +++ b/config.py @@ -19,10 +19,10 @@ debug = False saveInRoot = False # Version data -version = "1.27.3" +version = "1.28.2" tag = "git" -expansionName = "YC119.2" -expansionVersion = "1.4" +expansionName = "YC119.3" +expansionVersion = "1.0" evemonMinVersion = "4081" pyfaPath = None @@ -106,3 +106,7 @@ def defPaths(customSavePath): # saveddata db location modifier, shouldn't ever need to touch this eos.config.saveddata_connectionstring = "sqlite:///" + saveDB + "?check_same_thread=False" eos.config.gamedata_connectionstring = "sqlite:///" + gameDB + "?check_same_thread=False" + + # initialize the settings + from service.settings import EOSSettings + eos.config.settings = EOSSettings.getInstance().EOSSettings # this is kind of confusing, but whatever diff --git a/eos/config.py b/eos/config.py index 01d65cfe0..9dc0eabbc 100644 --- a/eos/config.py +++ b/eos/config.py @@ -1,15 +1,29 @@ import sys from os.path import realpath, join, dirname, abspath +from logbook import Logger +import os +istravis = os.environ.get('TRAVIS') == 'true' +pyfalog = Logger(__name__) + debug = False gamedataCache = True saveddataCache = True gamedata_version = "" -gamedata_connectionstring = 'sqlite:///' + unicode(realpath(join(dirname(abspath(__file__)), "..", "eve.db")), - sys.getfilesystemencoding()) -saveddata_connectionstring = 'sqlite:///' + unicode( - realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db")), sys.getfilesystemencoding()) +gamedata_connectionstring = 'sqlite:///' + unicode(realpath(join(dirname(abspath(__file__)), "..", "eve.db")), sys.getfilesystemencoding()) +pyfalog.debug("Gamedata connection string: {0}", gamedata_connectionstring) +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:///' + unicode(realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db")), sys.getfilesystemencoding()) + +pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring) + +settings = { + "setting1": True +} # Autodetect path, only change if the autodetection bugs out. path = dirname(unicode(__file__, sys.getfilesystemencoding())) diff --git a/eos/effects/tractorbeamcan.py b/eos/effects/tractorbeamcan.py index 293a839ea..2c7f8eae3 100644 --- a/eos/effects/tractorbeamcan.py +++ b/eos/effects/tractorbeamcan.py @@ -2,8 +2,10 @@ # # Used by: # Modules from group: Tractor Beam (4 of 4) +from eos.config import settings type = "active" def handler(fit, module, context): + print settings['setting1'] pass diff --git a/eos/gamedata.py b/eos/gamedata.py index c5a035b25..fe7eb2893 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -30,7 +30,10 @@ except ImportError: from utils.compat import OrderedDict from logbook import Logger + pyfalog = Logger(__name__) +# Keep a list of handlers that fail to import so we don't keep trying repeatedly. +badHandlers = [] class Effect(EqBase): @@ -158,43 +161,51 @@ class Effect(EqBase): Grab the handler, type and runTime from the effect code if it exists, if it doesn't, set dummy values and add a dummy handler """ - try: - self.__effectModule = effectModule = __import__('eos.effects.' + self.handlerName, fromlist=True) - try: - self.__handler = getattr(effectModule, "handler") - except AttributeError: - print "effect {} exists, but no handler".format(self.handlerName) - raise + global badHandlers + # Skip if we've tried to import before and failed + if self.handlerName not in badHandlers: try: - self.__runTime = getattr(effectModule, "runTime") or "normal" - except AttributeError: + self.__effectModule = effectModule = __import__('eos.effects.' + self.handlerName, fromlist=True) + self.__handler = getattr(effectModule, "handler", effectDummy) + self.__runTime = getattr(effectModule, "runTime", "normal") + self.__activeByDefault = getattr(effectModule, "activeByDefault", True) + t = getattr(effectModule, "type", None) + + t = t if isinstance(t, tuple) or t is None else (t,) + self.__type = t + except (ImportError) as e: + # Effect probably doesn't exist, so create a dummy effect and flag it with a warning. + self.__handler = effectDummy self.__runTime = "normal" - - try: - self.__activeByDefault = getattr(effectModule, "activeByDefault") - except AttributeError: self.__activeByDefault = True + self.__type = None + pyfalog.debug("ImportError generating handler: {0}", e) + badHandlers.append(self.handlerName) + except (AttributeError) as e: + # Effect probably exists but there is an issue with it. Turn it into a dummy effect so we can continue, but flag it with an error. + self.__handler = effectDummy + self.__runTime = "normal" + self.__activeByDefault = True + self.__type = None + pyfalog.error("AttributeError generating handler: {0}", e) + badHandlers.append(self.handlerName) + except Exception as e: + self.__handler = effectDummy + self.__runTime = "normal" + self.__activeByDefault = True + self.__type = None + pyfalog.critical("Exception generating handler:") + pyfalog.critical(e) + badHandlers.append(self.handlerName) - try: - t = getattr(effectModule, "type") - except AttributeError: - t = None - - t = t if isinstance(t, tuple) or t is None else (t,) - self.__type = t - except (ImportError, AttributeError) as e: + self.__generated = True + else: + # We've already failed on this one, just pass a dummy effect back self.__handler = effectDummy self.__runTime = "normal" self.__activeByDefault = True self.__type = None - pyfalog.debug("ImportError or AttributeError generating handler:") - pyfalog.debug(e) - except Exception as e: - pyfalog.critical("Exception generating handler:") - pyfalog.critical(e) - - self.__generated = True def getattr(self, key): if not self.__generated: @@ -292,8 +303,6 @@ class Item(EqBase): @property def requiredSkills(self): if self.__requiredSkills is None: - # This import should be here to make sure it's fully initialized - from eos import db requiredSkills = OrderedDict() self.__requiredSkills = requiredSkills # Map containing attribute IDs we may need for required skills @@ -304,7 +313,7 @@ class Item(EqBase): # { attributeID : attributeValue } skillAttrs = {} # Get relevant attribute values from db (required skill IDs and levels) for our item - for attrInfo in db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)): + for attrInfo in eos.db.directAttributeRequest((self.ID,), tuple(combinedAttrIDs)): attrID = attrInfo[1] attrVal = attrInfo[2] skillAttrs[attrID] = attrVal @@ -315,7 +324,7 @@ class Item(EqBase): skillID = int(skillAttrs[srqIDAtrr]) skillLvl = skillAttrs[srqLvlAttr] # Fetch item from database and fill map - item = db.getItem(skillID) + item = eos.db.getItem(skillID) requiredSkills[item] = skillLvl return self.__requiredSkills @@ -348,18 +357,20 @@ class Item(EqBase): # thus keep old mechanism for now except KeyError: # Define race map - map = {1: "caldari", - 2: "minmatar", - 4: "amarr", - 5: "sansha", # Caldari + Amarr - 6: "blood", # Minmatar + Amarr - 8: "gallente", - 9: "guristas", # Caldari + Gallente - 10: "angelserp", # Minmatar + Gallente, final race depends on the order of skills - 12: "sisters", # Amarr + Gallente - 16: "jove", - 32: "sansha", # Incrusion Sansha - 128: "ore"} + map = { + 1 : "caldari", + 2 : "minmatar", + 4 : "amarr", + 5 : "sansha", # Caldari + Amarr + 6 : "blood", # Minmatar + Amarr + 8 : "gallente", + 9 : "guristas", # Caldari + Gallente + 10 : "angelserp", # Minmatar + Gallente, final race depends on the order of skills + 12 : "sisters", # Amarr + Gallente + 16 : "jove", + 32 : "sansha", # Incrusion Sansha + 128: "ore" + } # Race is None by default race = None # Check primary and secondary required skills' races @@ -429,7 +440,7 @@ class Item(EqBase): def __repr__(self): return "Item(ID={}, name={}) at {}".format( - self.ID, self.name, hex(id(self)) + self.ID, self.name, hex(id(self)) ) @@ -454,7 +465,6 @@ class Category(EqBase): class AlphaClone(EqBase): - @reconstructor def init(self): self.skillCache = {} @@ -482,10 +492,9 @@ class Icon(EqBase): class MarketGroup(EqBase): - def __repr__(self): return u"MarketGroup(ID={}, name={}, parent={}) at {}".format( - self.ID, self.name, getattr(self.parent, "name", None), self.name, hex(id(self)) + self.ID, self.name, getattr(self.parent, "name", None), self.name, hex(id(self)) ).encode('utf8') diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 757d8a3d1..9658d6855 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -19,6 +19,9 @@ import collections from math import exp +# TODO: This needs to be moved out, we shouldn't have *ANY* dependencies back to other modules/methods inside eos. +# This also breaks writing any tests. :( +from eos.db.gamedata.queries import getAttributeInfo defaultValuesCache = {} cappingAttrKeyCache = {} @@ -26,22 +29,26 @@ cappingAttrKeyCache = {} class ItemAttrShortcut(object): def getModifiedItemAttr(self, key, default=None): - if key in self.itemModifiedAttributes: - return self.itemModifiedAttributes[key] - else: - return default + return_value = self.itemModifiedAttributes.get(key) + + if return_value is None and default is not None: + return_value = default + + return return_value class ChargeAttrShortcut(object): def getModifiedChargeAttr(self, key, default=None): - if key in self.chargeModifiedAttributes: - return self.chargeModifiedAttributes[key] - else: - return default + return_value = self.chargeModifiedAttributes.get(key) + + if return_value is None and default is not None: + return_value = default + + return return_value class ModifiedAttributeDict(collections.MutableMapping): - OVERRIDES = False + overrides_enabled = False class CalculationPlaceholder(object): def __init__(self): @@ -98,15 +105,23 @@ class ModifiedAttributeDict(collections.MutableMapping): def __getitem__(self, key): # Check if we have final calculated value - if key in self.__modified: - if self.__modified[key] == self.CalculationPlaceholder: - self.__modified[key] = self.__calculateValue(key) - return self.__modified[key] + key_value = self.__modified.get(key) + if key_value is self.CalculationPlaceholder: + key_value = self.__modified[key] = self.__calculateValue(key) + + if key_value is not None: + return key_value + # Then in values which are not yet calculated - elif key in self.__intermediary: - return self.__intermediary[key] - # Original value is the least priority + if self.__intermediary: + val = self.__intermediary.get(key) else: + val = None + + if val is not None: + return val + else: + # Original value is the least priority return self.getOriginal(key) def __delitem__(self, key): @@ -115,12 +130,18 @@ class ModifiedAttributeDict(collections.MutableMapping): if key in self.__intermediary: del self.__intermediary[key] - def getOriginal(self, key): - if self.OVERRIDES and key in self.__overrides: - return self.__overrides.get(key).value - val = self.__original.get(key) + def getOriginal(self, key, default=None): + if self.overrides_enabled and self.overrides: + val = self.overrides.get(key, None) + else: + val = None + if val is None: - return None + if self.original: + val = self.original.get(key, None) + + if val is None and val != default: + val = default return val.value if hasattr(val, "value") else val @@ -128,12 +149,12 @@ class ModifiedAttributeDict(collections.MutableMapping): self.__intermediary[key] = val def __iter__(self): - all = dict(self.__original, **self.__modified) - return (key for key in all) + all_dict = dict(self.original, **self.__modified) + return (key for key in all_dict) def __contains__(self, key): - return (self.__original is not None and key in self.__original) or \ - key in self.__modified or key in self.__intermediary + return (self.original is not None and key in self.original) or \ + key in self.__modified or key in self.__intermediary def __placehold(self, key): """Create calculation placeholder in item's modified attribute dict""" @@ -141,7 +162,7 @@ class ModifiedAttributeDict(collections.MutableMapping): def __len__(self): keys = set() - keys.update(self.__original.iterkeys()) + keys.update(self.original.iterkeys()) keys.update(self.__modified.iterkeys()) keys.update(self.__intermediary.iterkeys()) return len(keys) @@ -152,7 +173,6 @@ class ModifiedAttributeDict(collections.MutableMapping): try: cappingKey = cappingAttrKeyCache[key] except KeyError: - from eos.db.gamedata.queries import getAttributeInfo attrInfo = getAttributeInfo(key) if attrInfo is None: cappingId = cappingAttrKeyCache[key] = None @@ -166,12 +186,8 @@ class ModifiedAttributeDict(collections.MutableMapping): cappingKey = None if cappingAttrInfo is None else cappingAttrInfo.name if cappingKey: - if cappingKey in self.original: - # some items come with their own caps (ie: carriers). If they do, use this - cappingValue = self.original.get(cappingKey).value - else: - # If not, get info about the default value - cappingValue = self.__calculateValue(cappingKey) + cappingValue = self.original.get(cappingKey, self.__calculateValue(cappingKey)) + cappingValue = cappingValue.value if hasattr(cappingValue, "value") else cappingValue else: cappingValue = None @@ -183,25 +199,28 @@ class ModifiedAttributeDict(collections.MutableMapping): force = min(force, cappingValue) return force # Grab our values if they're there, otherwise we'll take default values - preIncrease = self.__preIncreases[key] if key in self.__preIncreases else 0 - multiplier = self.__multipliers[key] if key in self.__multipliers else 1 - penalizedMultiplierGroups = self.__penalizedMultipliers[key] if key in self.__penalizedMultipliers else {} - postIncrease = self.__postIncreases[key] if key in self.__postIncreases else 0 + preIncrease = self.__preIncreases.get(key, 0) + multiplier = self.__multipliers.get(key, 1) + penalizedMultiplierGroups = self.__penalizedMultipliers.get(key, {}) + postIncrease = self.__postIncreases.get(key, 0) # Grab initial value, priorities are: # Results of ongoing calculation > preAssign > original > 0 try: default = defaultValuesCache[key] except KeyError: - from eos.db.gamedata.queries import getAttributeInfo attrInfo = getAttributeInfo(key) if attrInfo is None: default = defaultValuesCache[key] = 0.0 else: dv = attrInfo.defaultValue default = defaultValuesCache[key] = dv if dv is not None else 0.0 - val = self.__intermediary[key] if key in self.__intermediary else self.__preAssigns[ - key] if key in self.__preAssigns else self.getOriginal(key) if key in self.__original else default + + val = self.__intermediary.get(key, + self.__preAssigns.get(key, + self.getOriginal(key, default) + ) + ) # We'll do stuff in the following order: # preIncrease > multiplier > stacking penalized multipliers > postIncrease @@ -254,7 +273,7 @@ class ModifiedAttributeDict(collections.MutableMapping): return skill.level def getAfflictions(self, key): - return self.__affectedBy[key] if key in self.__affectedBy else {} + return self.__affectedBy.get(key, {}) def iterAfflictions(self): return self.__affectedBy.__iter__() @@ -360,6 +379,6 @@ class ModifiedAttributeDict(collections.MutableMapping): class Affliction(object): - def __init__(self, type, amount): - self.type = type + def __init__(self, affliction_type, amount): + self.type = affliction_type self.amount = amount diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index aa92ff21a..5f2695710 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -128,9 +128,9 @@ class Fit(object): self.__capRecharge = None self.__calculatedTargets = [] self.__remoteReps = { - "Armor": None, - "Shield": None, - "Hull": None, + "Armor" : None, + "Shield" : None, + "Hull" : None, "Capacitor": None, } self.factorReload = False @@ -370,9 +370,11 @@ class Fit(object): @validates("ID", "ownerID", "shipID") def validator(self, key, val): - map = {"ID": lambda _val: isinstance(_val, int), - "ownerID": lambda _val: isinstance(_val, int) or _val is None, - "shipID": lambda _val: isinstance(_val, int) or _val is None} + map = { + "ID" : lambda _val: isinstance(_val, int), + "ownerID": lambda _val: isinstance(_val, int) or _val is None, + "shipID" : lambda _val: isinstance(_val, int) or _val is None + } if not map[key](val): raise ValueError(str(val) + " is not a valid value for " + key) @@ -408,15 +410,15 @@ class Fit(object): self.ship.clear() c = chain( - self.modules, - self.drones, - self.fighters, - self.boosters, - self.implants, - self.projectedDrones, - self.projectedModules, - self.projectedFighters, - (self.character, self.extraAttributes), + self.modules, + self.drones, + self.fighters, + self.boosters, + self.implants, + self.projectedDrones, + self.projectedModules, + self.projectedFighters, + (self.character, self.extraAttributes), ) for stuff in c: @@ -476,11 +478,11 @@ class Fit(object): if warfareBuffID == 11: # Shield Burst: Active Shielding: Repair Duration/Capacitor self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( - "Shield Emission Systems"), "capacitorNeed", value) + lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( + "Shield Emission Systems"), "capacitorNeed", value) self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( - "Shield Emission Systems"), "duration", value) + lambda mod: mod.item.requiresSkill("Shield Operation") or mod.item.requiresSkill( + "Shield Emission Systems"), "duration", value) if warfareBuffID == 12: # Shield Burst: Shield Extension: Shield HP self.ship.boostItemAttr("shieldCapacity", value, stackingPenalties=True) @@ -506,26 +508,26 @@ class Fit(object): if warfareBuffID == 17: # Information Burst: Electronic Superiority: EWAR Range and Strength groups = ("ECM", "Sensor Dampener", "Weapon Disruptor", "Target Painter") self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, - stackingPenalties=True) + stackingPenalties=True) self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, - "falloffEffectiveness", value, stackingPenalties=True) + "falloffEffectiveness", value, stackingPenalties=True) for scanType in ("Magnetometric", "Radar", "Ladar", "Gravimetric"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "ECM", - "scan%sStrengthBonus" % scanType, value, - stackingPenalties=True) + "scan%sStrengthBonus" % scanType, value, + stackingPenalties=True) for attr in ("missileVelocityBonus", "explosionDelayBonus", "aoeVelocityBonus", "falloffBonus", "maxRangeBonus", "aoeCloudSizeBonus", "trackingSpeedBonus"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Weapon Disruptor", - attr, value) + attr, value) for attr in ("maxTargetRangeBonus", "scanResolutionBonus"): self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Sensor Dampener", - attr, value) + attr, value) self.modules.filteredItemBoost(lambda mod: mod.item.group.name == "Target Painter", - "signatureRadiusBonus", value, stackingPenalties=True) + "signatureRadiusBonus", value, stackingPenalties=True) if warfareBuffID == 18: # Information Burst: Electronic Hardening: Scan Strength for scanType in ("Gravimetric", "Radar", "Ladar", "Magnetometric"): @@ -543,35 +545,33 @@ class Fit(object): if warfareBuffID == 21: # Skirmish Burst: Interdiction Maneuvers: Tackle Range groups = ("Stasis Web", "Warp Scrambler") - self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, - stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.group.name in groups, "maxRange", value, stackingPenalties=True) if warfareBuffID == 22: # Skirmish Burst: Rapid Deployment: AB/MWD Speed Increase - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Afterburner") or mod.item.requiresSkill( - "High Speed Maneuvering"), "speedFactor", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Afterburner") or mod.item.requiresSkill("High Speed Maneuvering"), + "speedFactor", value, stackingPenalties=True) if warfareBuffID == 23: # Mining Burst: Mining Laser Field Enhancement: Mining/Survey Range - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( - "Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), "maxRange", - value, stackingPenalties=True) - self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"), - "surveyScanRange", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "maxRange", value, stackingPenalties=True) + + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("CPU Management"), "surveyScanRange", value, stackingPenalties=True) if warfareBuffID == 24: # Mining Burst: Mining Laser Optimization: Mining Capacitor/Duration - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( - "Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), - "capacitorNeed", value, stackingPenalties=True) - self.modules.filteredItemBoost( - lambda mod: mod.item.requiresSkill("Mining") or mod.item.requiresSkill( - "Ice Harvesting") or mod.item.requiresSkill("Gas Cloud Harvesting"), "duration", - value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "capacitorNeed", value, stackingPenalties=True) + + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining") or + mod.item.requiresSkill("Ice Harvesting") or + mod.item.requiresSkill("Gas Cloud Harvesting"), + "duration", value, stackingPenalties=True) if warfareBuffID == 25: # Mining Burst: Mining Equipment Preservation: Crystal Volatility - self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"), - "crystalVolatilityChance", value, stackingPenalties=True) + self.modules.filteredItemBoost(lambda mod: mod.item.requiresSkill("Mining"), "crystalVolatilityChance", value, stackingPenalties=True) if warfareBuffID == 60: # Skirmish Burst: Evasive Maneuvers: Agility self.ship.boostItemAttr("agility", value, stackingPenalties=True) @@ -844,15 +844,17 @@ class Fit(object): return amount - slots = {Slot.LOW: "lowSlots", - Slot.MED: "medSlots", - Slot.HIGH: "hiSlots", - Slot.RIG: "rigSlots", - Slot.SUBSYSTEM: "maxSubSystems", - Slot.SERVICE: "serviceSlots", - Slot.F_LIGHT: "fighterLightSlots", - Slot.F_SUPPORT: "fighterSupportSlots", - Slot.F_HEAVY: "fighterHeavySlots"} + slots = { + Slot.LOW : "lowSlots", + Slot.MED : "medSlots", + Slot.HIGH : "hiSlots", + Slot.RIG : "rigSlots", + Slot.SUBSYSTEM: "maxSubSystems", + Slot.SERVICE : "serviceSlots", + Slot.F_LIGHT : "fighterLightSlots", + Slot.F_SUPPORT: "fighterSupportSlots", + Slot.F_HEAVY : "fighterHeavySlots" + } def getSlotsFree(self, type, countDummies=False): if type in (Slot.MODE, Slot.SYSTEM): @@ -927,20 +929,19 @@ class Fit(object): return amount - # Expresses how difficult a target is to probe down with scan probes - # If this is <1.08, the ship is unproabeable @property def probeSize(self): + """ + Expresses how difficult a target is to probe down with scan probes + """ + sigRad = self.ship.getModifiedItemAttr("signatureRadius") sensorStr = float(self.scanStrength) probeSize = sigRad / sensorStr if sensorStr != 0 else None # http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1532170&page=2#42 if probeSize is not None: - # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333691 - # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333763 - # Tests by tester128 and several conclusions by me, prove that cap is in range - # from 1.1 to 1.12, we're picking average value - probeSize = max(probeSize, 1.11) + # Probe size is capped at 1.08 + probeSize = max(probeSize, 1.08) return probeSize @property @@ -1002,29 +1003,35 @@ class Fit(object): def calculateSustainableTank(self, effective=True): if self.__sustainableTank is None: if self.capStable: - sustainable = {"armorRepair": self.extraAttributes["armorRepair"], - "shieldRepair": self.extraAttributes["shieldRepair"], - "hullRepair": self.extraAttributes["hullRepair"]} + sustainable = { + "armorRepair" : self.extraAttributes["armorRepair"], + "shieldRepair": self.extraAttributes["shieldRepair"], + "hullRepair" : self.extraAttributes["hullRepair"] + } else: sustainable = {} repairers = [] # Map a repairer type to the attribute it uses - groupAttrMap = {"Armor Repair Unit": "armorDamageAmount", - "Ancillary Armor Repairer": "armorDamageAmount", - "Hull Repair Unit": "structureDamageAmount", - "Shield Booster": "shieldBonus", - "Ancillary Shield Booster": "shieldBonus", - "Remote Armor Repairer": "armorDamageAmount", - "Remote Shield Booster": "shieldBonus"} + groupAttrMap = { + "Armor Repair Unit" : "armorDamageAmount", + "Ancillary Armor Repairer": "armorDamageAmount", + "Hull Repair Unit" : "structureDamageAmount", + "Shield Booster" : "shieldBonus", + "Ancillary Shield Booster": "shieldBonus", + "Remote Armor Repairer" : "armorDamageAmount", + "Remote Shield Booster" : "shieldBonus" + } # Map repairer type to attribute - groupStoreMap = {"Armor Repair Unit": "armorRepair", - "Hull Repair Unit": "hullRepair", - "Shield Booster": "shieldRepair", - "Ancillary Shield Booster": "shieldRepair", - "Remote Armor Repairer": "armorRepair", - "Remote Shield Booster": "shieldRepair", - "Ancillary Armor Repairer": "armorRepair", } + groupStoreMap = { + "Armor Repair Unit" : "armorRepair", + "Hull Repair Unit" : "hullRepair", + "Shield Booster" : "shieldRepair", + "Ancillary Shield Booster": "shieldRepair", + "Remote Armor Repairer" : "armorRepair", + "Remote Shield Booster" : "shieldRepair", + "Ancillary Armor Repairer": "armorRepair", + } capUsed = self.capUsed for attr in ("shieldRepair", "armorRepair", "hullRepair"): @@ -1052,7 +1059,7 @@ class Fit(object): # Sort repairers by efficiency. We want to use the most efficient repairers first repairers.sort(key=lambda _mod: _mod.getModifiedItemAttr( - groupAttrMap[_mod.item.group.name]) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True) + groupAttrMap[_mod.item.group.name]) / _mod.getModifiedItemAttr("capacitorNeed"), reverse=True) # Loop through every module until we're above peak recharge # Most efficient first, as we sorted earlier. @@ -1365,10 +1372,10 @@ class Fit(object): def __repr__(self): return u"Fit(ID={}, ship={}, name={}) at {}".format( - self.ID, self.ship.item.name, self.name, hex(id(self)) + self.ID, self.ship.item.name, self.name, hex(id(self)) ).encode('utf8') def __str__(self): return u"{} ({})".format( - self.name, self.ship.item.name + self.name, self.ship.item.name ).encode('utf8') diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index 4442cb9b8..8287f349a 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -173,7 +173,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): charges = 0 else: charges = floor(containerCapacity / chargeVolume) - return charges + return int(charges) @property def numShots(self): @@ -689,7 +689,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): Currently would apply to bomb launchers and defender missiles """ - effective_reload_time = ((self.reactivationDelay * numShots) + raw_reload_time) / numShots + effective_reload_time = ((self.reactivationDelay * (numShots - 1)) + max(raw_reload_time, self.reactivationDelay, 0)) / numShots else: """ Applies to MJD/MJFG diff --git a/eve.db b/eve.db index 1c5c07a91..f0717cc7c 100644 Binary files a/eve.db and b/eve.db differ diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py index bb8631623..e69de29bb 100644 --- a/gui/builtinContextMenus/__init__.py +++ b/gui/builtinContextMenus/__init__.py @@ -1,27 +0,0 @@ -__all__ = [ - "openFit", - # "moduleGlobalAmmoPicker", - "moduleAmmoPicker", - "itemStats", - "damagePattern", - "marketJump", - "droneSplit", - "itemRemove", - "droneRemoveStack", - "ammoPattern", - "project", - "factorReload", - "whProjector", - "cargo", - "shipJump", - "changeAffectingSkills", - "tacticalMode", - "targetResists", - "priceClear", - "amount", - "metaSwap", - "implantSets", - "fighterAbilities", - "cargoAmmo", - "droneStack" -] diff --git a/gui/builtinContextMenus/cargoAmmo.py b/gui/builtinContextMenus/cargoAmmo.py index 573e4c24f..12ebe552f 100644 --- a/gui/builtinContextMenus/cargoAmmo.py +++ b/gui/builtinContextMenus/cargoAmmo.py @@ -1,15 +1,20 @@ from gui.contextMenu import ContextMenu import gui.mainFrame -import service import gui.globalEvents as GE import wx +from service.settings import ContextMenuSettings +from service.fit import Fit class CargoAmmo(ContextMenu): def __init__(self): self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.settings = ContextMenuSettings.getInstance() def display(self, srcContext, selection): + if not self.settings.get('cargoAmmo'): + return False + if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None: return False @@ -23,7 +28,7 @@ class CargoAmmo(ContextMenu): return "Add {0} to Cargo (x1000)".format(itmContext) def activate(self, fullContext, selection, i): - sFit = service.Fit.getInstance() + sFit = Fit.getInstance() fitID = self.mainFrame.getActiveFit() typeID = int(selection[0].ID) diff --git a/gui/builtinContextMenus/droneStack.py b/gui/builtinContextMenus/droneStack.py index b7fde1040..99a253796 100644 --- a/gui/builtinContextMenus/droneStack.py +++ b/gui/builtinContextMenus/droneStack.py @@ -1,15 +1,20 @@ from gui.contextMenu import ContextMenu import gui.mainFrame -import service import gui.globalEvents as GE import wx +from service.settings import ContextMenuSettings +from service.fit import Fit -class CargoAmmo(ContextMenu): +class DroneStack(ContextMenu): def __init__(self): self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.settings = ContextMenuSettings.getInstance() def display(self, srcContext, selection): + if not self.settings.get('droneStack'): + return False + if srcContext not in ("marketItemGroup", "marketItemMisc") or self.mainFrame.getActiveFit() is None: return False @@ -25,7 +30,7 @@ class CargoAmmo(ContextMenu): return "Add {0} to Drone Bay (x5)".format(itmContext) def activate(self, fullContext, selection, i): - sFit = service.Fit.getInstance() + sFit = Fit.getInstance() fitID = self.mainFrame.getActiveFit() typeID = int(selection[0].ID) @@ -34,4 +39,4 @@ class CargoAmmo(ContextMenu): wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) -CargoAmmo.register() +DroneStack.register() diff --git a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py index ca87d3de0..58ed81556 100644 --- a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py +++ b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py @@ -108,7 +108,7 @@ class PFContextMenuPref(PreferenceView): self.settings.set('project', event.GetInt()) def getImage(self): - return BitmapLoader.getBitmap("pref-gauges_big", "gui") + return BitmapLoader.getBitmap("settings_menu", "gui") PFContextMenuPref.register() diff --git a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py index 95c2e3263..b8647ef2f 100644 --- a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py +++ b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py @@ -85,7 +85,7 @@ class PFGeneralPref(PreferenceView): ''' def getImage(self): - return BitmapLoader.getBitmap("prefs_settings", "gui") + return BitmapLoader.getBitmap("settings_database", "gui") def OnWindowLeave(self, event): # We don't want to do anything when they leave, diff --git a/gui/builtinPreferenceViews/pyfaEnginePreferences.py b/gui/builtinPreferenceViews/pyfaEnginePreferences.py index 3d84a4367..415264436 100644 --- a/gui/builtinPreferenceViews/pyfaEnginePreferences.py +++ b/gui/builtinPreferenceViews/pyfaEnginePreferences.py @@ -82,7 +82,7 @@ class PFFittingEnginePref(PreferenceView): self.sFit.serviceFittingOptions["useStaticAdaptiveArmorHardener"] = self.cbUniversalAdaptiveArmorHardener.GetValue() def getImage(self): - return BitmapLoader.getBitmap("prefs_settings", "gui") + return BitmapLoader.getBitmap("settings_fitting", "gui") def OnWindowLeave(self, event): # We don't want to do anything when they leave, diff --git a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py index 601f535b0..8aa140f97 100644 --- a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py +++ b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py @@ -76,7 +76,7 @@ class PFGeneralPref(PreferenceView): ''' def getImage(self): - return BitmapLoader.getBitmap("prefs_settings", "gui") + return BitmapLoader.getBitmap("settings_log", "gui") PFGeneralPref.register() diff --git a/gui/builtinPreferenceViews/pyfaStatViewPreferences.py b/gui/builtinPreferenceViews/pyfaStatViewPreferences.py index 601e135f6..64182a82e 100644 --- a/gui/builtinPreferenceViews/pyfaStatViewPreferences.py +++ b/gui/builtinPreferenceViews/pyfaStatViewPreferences.py @@ -81,7 +81,7 @@ class PFStatViewPref(PreferenceView): self.rbMisc = wx.RadioBox(panel, -1, "Misc", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS) # Disable full as we don't have a view for this yet self.rbMisc.EnableItem(2, False) - self.rbMisc.SetSelection(self.settings.get('targetingmisc')) + self.rbMisc.SetSelection(self.settings.get('targetingMisc')) rbSizerRow2.Add(self.rbMisc, 1, wx.ALL, 5) self.rbMisc.Bind(wx.EVT_RADIOBOX, self.OnTargetingMiscChange) @@ -97,6 +97,10 @@ class PFStatViewPref(PreferenceView): rbSizerRow3.Add(self.rbPrice, 1, wx.TOP | wx.RIGHT, 5) self.rbPrice.Bind(wx.EVT_RADIOBOX, self.OnPriceChange) + self.rbOutgoing = wx.RadioBox(panel, -1, "Remote Reps", wx.DefaultPosition, wx.DefaultSize, ['None', 'Minimal', 'Full'], 1, wx.RA_SPECIFY_COLS) + self.rbOutgoing.SetSelection(self.settings.get('outgoing')) + rbSizerRow3.Add(self.rbOutgoing, 1, wx.TOP | wx.RIGHT, 5) + self.rbOutgoing.Bind(wx.EVT_RADIOBOX, self.OnOutgoingChange) # We don't have views for these.....yet ''' self.rbMining = wx.RadioBox(panel, -1, "Mining", wx.DefaultPosition, wx.DefaultSize, @@ -133,11 +137,14 @@ class PFStatViewPref(PreferenceView): self.settings.set('capacitor', event.GetInt()) def OnTargetingMiscChange(self, event): - self.settings.set('targetingmisc', event.GetInt()) + self.settings.set('targetingMisc', event.GetInt()) def OnPriceChange(self, event): self.settings.set('price', event.GetInt()) + def OnOutgoingChange(self, event): + self.settings.set('outgoing', event.GetInt()) + def OnMiningYieldChange(self, event): self.settings.set('miningyield', event.GetInt()) @@ -145,7 +152,7 @@ class PFStatViewPref(PreferenceView): self.settings.set('drones', event.GetInt()) def getImage(self): - return BitmapLoader.getBitmap("pref-gauges_big", "gui") + return BitmapLoader.getBitmap("settings_stats", "gui") PFStatViewPref.register() diff --git a/gui/builtinStatsViews/__init__.py b/gui/builtinStatsViews/__init__.py index 27cc3e1a2..be310c2ac 100644 --- a/gui/builtinStatsViews/__init__.py +++ b/gui/builtinStatsViews/__init__.py @@ -5,6 +5,7 @@ __all__ = [ "firepowerViewFull", "capacitorViewFull", "outgoingViewFull", + "outgoingViewMinimal", "targetingMiscViewMinimal", "priceViewFull", ] diff --git a/gui/builtinStatsViews/capacitorViewFull.py b/gui/builtinStatsViews/capacitorViewFull.py index f85b35e4c..49bb9d59b 100644 --- a/gui/builtinStatsViews/capacitorViewFull.py +++ b/gui/builtinStatsViews/capacitorViewFull.py @@ -114,6 +114,10 @@ class CapacitorViewFull(StatsView): ("label%sCapacitorRecharge", lambda: fit.capRecharge, 3, 0, 0), ("label%sCapacitorDischarge", lambda: fit.capUsed, 3, 0, 0), ) + if fit: + neut_resist = fit.ship.getModifiedItemAttr("energyWarfareResistance", 0) + else: + neut_resist = 0 panel = "Full" for labelName, value, prec, lowest, highest in stats: @@ -127,6 +131,12 @@ class CapacitorViewFull(StatsView): label.SetLabel(formatAmount(value, prec, lowest, highest)) label.SetToolTip(wx.ToolTip("%.1f" % value)) + if labelName == "label%sCapacitorDischarge": + if neut_resist: + neut_resist = 100 - (neut_resist * 100) + label_tooltip = "Neut Resistance: {0:.0f}%".format(neut_resist) + label.SetToolTip(wx.ToolTip(label_tooltip)) + capState = fit.capState if fit is not None else 0 capStable = fit.capStable if fit is not None else False lblNameTime = "label%sCapacitorTime" diff --git a/gui/builtinStatsViews/outgoingViewMinimal.py b/gui/builtinStatsViews/outgoingViewMinimal.py index 346aacc7a..d2bf2c118 100644 --- a/gui/builtinStatsViews/outgoingViewMinimal.py +++ b/gui/builtinStatsViews/outgoingViewMinimal.py @@ -20,12 +20,11 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader from gui.utils.numberFormatter import formatAmount -class OutgoingViewFull(StatsView): - name = "outgoingViewFull" +class OutgoingViewMinimal(StatsView): + name = "outgoingViewMinimal" def __init__(self, parent): StatsView.__init__(self) @@ -60,7 +59,7 @@ class OutgoingViewFull(StatsView): for outgoingType, label, image, tooltip in rr_list: baseBox = wx.BoxSizer(wx.VERTICAL) - baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER) + baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 0, wx.ALIGN_CENTER) if "Capacitor" in outgoingType: lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s") @@ -103,4 +102,4 @@ class OutgoingViewFull(StatsView): self.headerPanel.Layout() -OutgoingViewFull.register() +OutgoingViewMinimal.register() diff --git a/gui/builtinStatsViews/targetingMiscViewFull.py b/gui/builtinStatsViews/targetingMiscViewFull.py index 27e83fea8..1fa235ba0 100644 --- a/gui/builtinStatsViews/targetingMiscViewFull.py +++ b/gui/builtinStatsViews/targetingMiscViewFull.py @@ -29,7 +29,7 @@ except ImportError: class TargetingMiscViewFull(StatsView): - name = "targetingmiscViewFull" + name = "targetingMiscViewFull" def __init__(self, parent): StatsView.__init__(self) diff --git a/gui/builtinStatsViews/targetingMiscViewMinimal.py b/gui/builtinStatsViews/targetingMiscViewMinimal.py index 458a3ab64..4dffc9e40 100644 --- a/gui/builtinStatsViews/targetingMiscViewMinimal.py +++ b/gui/builtinStatsViews/targetingMiscViewMinimal.py @@ -191,8 +191,6 @@ class TargetingMiscViewMinimal(StatsView): right = "%s [%d]" % (size, radius) lockTime += "%5s\t%s\n" % (left, right) label.SetToolTip(wx.ToolTip(lockTime)) - elif labelName == "labelFullSigRadius": - label.SetToolTip(wx.ToolTip("Probe Size: %.3f" % (fit.probeSize or 0))) elif labelName == "labelFullWarpSpeed": label.SetToolTip(wx.ToolTip("Max Warp Distance: %.1f AU" % fit.maxWarpDistance)) elif labelName == "labelSensorStr": @@ -242,8 +240,15 @@ class TargetingMiscViewMinimal(StatsView): else: label.SetToolTip(wx.ToolTip("")) - counter += 1 + # forces update of probe size, since this stat is used by both sig radius and sensor str + if labelName == "labelFullSigRadius": + print "labelName" + if fit: + label.SetToolTip(wx.ToolTip("Probe Size: %.3f" % (fit.probeSize or 0))) + else: + label.SetToolTip(wx.ToolTip("")) + counter += 1 self.panel.Layout() self.headerPanel.Layout() diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index 6555c6ce5..496cf4fb8 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -55,15 +55,16 @@ class FitSpawner(gui.multiSwitch.TabSpawner): def fitSelected(self, event): count = -1 for index, page in enumerate(self.multiSwitch.pages): - try: - if page.activeFitID == event.fitID: - count += 1 - self.multiSwitch.SetSelection(index) - wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=event.fitID)) - break - except Exception as e: - pyfalog.critical("Caught exception in fitSelected") - pyfalog.critical(e) + if not isinstance(page, gui.builtinViews.emptyView.BlankPage): # Don't try and process it if it's a blank page. + try: + if page.activeFitID == event.fitID: + count += 1 + self.multiSwitch.SetSelection(index) + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=event.fitID)) + break + except Exception as e: + pyfalog.critical("Caught exception in fitSelected") + pyfalog.critical(e) if count < 0: startup = getattr(event, "startup", False) # see OpenFitsThread in gui.mainFrame sFit = Fit.getInstance() @@ -149,7 +150,6 @@ class FittingView(d.Display): self.activeFitID = None self.FVsnapshot = None self.itemCount = 0 - self.itemRect = 0 self.hoveredRow = None self.hoveredColumn = None @@ -268,9 +268,7 @@ class FittingView(d.Display): We also refresh the fit of the new current page in case delete fit caused change in stats (projected) """ - fitID = event.fitID - - if fitID == self.getActiveFit(): + if event.fitID == self.getActiveFit(): self.parent.DeletePage(self.parent.GetPageIndex(self)) try: @@ -279,7 +277,7 @@ class FittingView(d.Display): sFit.refreshFit(self.getActiveFit()) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID)) except wx._core.PyDeadObjectError: - pyfalog.warning("Caught dead object") + pyfalog.error("Caught dead object") pass event.Skip() @@ -408,7 +406,7 @@ class FittingView(d.Display): if mod1.slot != mod2.slot: return - if getattr(mod2, "modPosition"): + if getattr(mod2, "modPosition") is not None: if clone and mod2.isEmpty: sFit.cloneModule(self.mainFrame.getActiveFit(), srcIdx, mod2.modPosition) else: @@ -485,7 +483,7 @@ class FittingView(d.Display): self.Show(self.activeFitID is not None and self.activeFitID == event.fitID) except wx._core.PyDeadObjectError: - pyfalog.warning("Caught dead object") + pyfalog.error("Caught dead object") finally: event.Skip() @@ -633,7 +631,6 @@ class FittingView(d.Display): self.Thaw() self.itemCount = self.GetItemCount() - self.itemRect = self.GetItemRect(0) if 'wxMac' in wx.PlatformInfo: try: diff --git a/gui/contextMenu.py b/gui/contextMenu.py index 243a4a743..a35c73282 100644 --- a/gui/contextMenu.py +++ b/gui/contextMenu.py @@ -181,7 +181,7 @@ class ContextMenu(object): # noinspection PyUnresolvedReferences from gui.builtinContextMenus import ( # noqa: E402,F401 openFit, - # moduleGlobalAmmoPicker, + moduleGlobalAmmoPicker, moduleAmmoPicker, itemStats, damagePattern, @@ -200,6 +200,8 @@ from gui.builtinContextMenus import ( # noqa: E402,F401 targetResists, priceClear, amount, + cargoAmmo, + droneStack, metaSwap, implantSets, fighterAbilities, diff --git a/gui/graphFrame.py b/gui/graphFrame.py index 7a0aa3387..2abeadbab 100644 --- a/gui/graphFrame.py +++ b/gui/graphFrame.py @@ -29,13 +29,14 @@ import gui.mainFrame import gui.globalEvents as GE from gui.graph import Graph from gui.bitmapLoader import BitmapLoader +import traceback pyfalog = Logger(__name__) try: import matplotlib as mpl - mpl_version = int(mpl.__version__[0]) + mpl_version = int(mpl.__version__[0]) or -1 if mpl_version >= 2: mpl.use('wxagg') mplImported = True @@ -48,43 +49,33 @@ try: graphFrame_enabled = True mplImported = True -except ImportError: +except ImportError as e: + pyfalog.warning("Matplotlib failed to import. Likely missing or incompatible version.") + mpl_version = -1 + Patch = mpl = Canvas = Figure = None + graphFrame_enabled = False + mplImported = False +except Exception: + # We can get exceptions deep within matplotlib. Catch those. See GH #1046 + tb = traceback.format_exc() + pyfalog.critical("Exception when importing Matplotlib. Continuing without importing.") + pyfalog.critical(tb) + mpl_version = -1 Patch = mpl = Canvas = Figure = None graphFrame_enabled = False mplImported = False -pyfalog = Logger(__name__) - class GraphFrame(wx.Frame): def __init__(self, parent, style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE | wx.FRAME_FLOAT_ON_PARENT): - global graphFrame_enabled global mplImported - - self.Patch = None - self.mpl_version = -1 - - try: - import matplotlib as mpl - self.mpl_version = int(mpl.__version__[0]) - if self.mpl_version >= 2: - mpl.use('wxagg') - mplImported = True - else: - mplImported = False - from matplotlib.patches import Patch - self.Patch = Patch - from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as Canvas - from matplotlib.figure import Figure - graphFrame_enabled = True - except ImportError: - Patch = mpl = Canvas = Figure = None - graphFrame_enabled = False + global mpl_version self.legendFix = False + if not graphFrame_enabled: - pyfalog.info("Problems importing matplotlib; continuing without graphs") + pyfalog.warning("Matplotlib is not enabled. Skipping initialization.") return try: @@ -236,6 +227,8 @@ class GraphFrame(wx.Frame): self.draw() def draw(self, event=None): + global mpl_version + values = self.getValues() view = self.getView() self.subplot.clear() @@ -260,7 +253,7 @@ class GraphFrame(wx.Frame): self.canvas.draw() return - if self.mpl_version < 2: + if mpl_version < 2: if self.legendFix and len(legend) > 0: leg = self.subplot.legend(tuple(legend), "upper right", shadow=False) for t in leg.get_texts(): @@ -276,7 +269,7 @@ class GraphFrame(wx.Frame): for l in leg.get_lines(): l.set_linewidth(1) - elif self.mpl_version >= 2: + elif mpl_version >= 2: legend2 = [] legend_colors = { 0: "blue", diff --git a/gui/itemStats.py b/gui/itemStats.py index 325793e86..3ac94ce0c 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -1043,7 +1043,7 @@ class ItemAffectedBy(wx.Panel): container = {} for attrName in attributes.iterAfflictions(): # if value is 0 or there has been no change from original to modified, return - if attributes[attrName] == (attributes.getOriginal(attrName) or 0): + if attributes[attrName] == (attributes.getOriginal(attrName, 0)): continue for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): @@ -1170,7 +1170,7 @@ class ItemAffectedBy(wx.Panel): container = {} for attrName in attributes.iterAfflictions(): # if value is 0 or there has been no change from original to modified, return - if attributes[attrName] == (attributes.getOriginal(attrName) or 0): + if attributes[attrName] == (attributes.getOriginal(attrName, 0)): continue for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 7af37d875..8a66e78d2 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -651,11 +651,11 @@ class MainFrame(wx.Frame): dlg.Show() def toggleOverrides(self, event): - ModifiedAttributeDict.OVERRIDES = not ModifiedAttributeDict.OVERRIDES + ModifiedAttributeDict.overrides_enabled = not ModifiedAttributeDict.overrides_enabled wx.PostEvent(self, GE.FitChanged(fitID=self.getActiveFit())) menu = self.GetMenuBar() menu.SetLabel(menu.toggleOverridesId, - "Turn Overrides Off" if ModifiedAttributeDict.OVERRIDES else "Turn Overrides On") + "Turn Overrides Off" if ModifiedAttributeDict.overrides_enabled else "Turn Overrides On") def saveChar(self, event): sChr = Character.getInstance() diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 466c768cb..4fe421082 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -1559,6 +1559,10 @@ class FitItem(SFItem.SFBrowserItem): self.selTimer.Start(100) self.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) + self.Bind(wx.EVT_MIDDLE_UP, self.OpenNewTab) + + def OpenNewTab(self, evt): + self.selectFit(newTab=True) def OnToggleBooster(self, event): sFit = Fit.getInstance() @@ -1623,6 +1627,9 @@ class FitItem(SFItem.SFBrowserItem): # menu.AppendSubMenu(boosterMenu, 'Set Booster') if fit: + newTabItem = menu.Append(wx.ID_ANY, "Open in new tab") + self.Bind(wx.EVT_MENU, self.OpenNewTab, newTabItem) + projectedItem = menu.Append(wx.ID_ANY, "Project onto Active Fit") self.Bind(wx.EVT_MENU, self.OnProjectToFit, projectedItem) @@ -1743,6 +1750,7 @@ class FitItem(SFItem.SFBrowserItem): self.deleteFit() def deleteFit(self, event=None): + pyfalog.debug("Deleting ship fit.") if self.deleted: return else: @@ -1816,8 +1824,11 @@ class FitItem(SFItem.SFBrowserItem): self.dragWindow.SetPosition(pos) return - def selectFit(self, event=None): - wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID)) + def selectFit(self, event=None, newTab=False): + if newTab: + wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID, startup=2)) + else: + wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID)) def RestoreEditButton(self): self.tcFitName.Show(False) diff --git a/gui/statsPane.py b/gui/statsPane.py index 181d8f9c5..ce09e2e62 100644 --- a/gui/statsPane.py +++ b/gui/statsPane.py @@ -40,7 +40,7 @@ class StatsPane(wx.Panel): "resistances", "recharge", "firepower", - "outgoingView", + "outgoing", "capacitor", "targetingMisc", "price", diff --git a/gui/statsView.py b/gui/statsView.py index e8b614427..464359695 100644 --- a/gui/statsView.py +++ b/gui/statsView.py @@ -53,4 +53,5 @@ from gui.builtinStatsViews import ( # noqa: E402, F401 targetingMiscViewMinimal, priceViewFull, outgoingViewFull, + outgoingViewMinimal, ) diff --git a/imgs/gui/settings_database.png b/imgs/gui/settings_database.png new file mode 100644 index 000000000..373a0adb0 Binary files /dev/null and b/imgs/gui/settings_database.png differ diff --git a/imgs/gui/settings_fitting.png b/imgs/gui/settings_fitting.png new file mode 100644 index 000000000..60026f35b Binary files /dev/null and b/imgs/gui/settings_fitting.png differ diff --git a/imgs/gui/settings_log.png b/imgs/gui/settings_log.png new file mode 100644 index 000000000..041f8ebb8 Binary files /dev/null and b/imgs/gui/settings_log.png differ diff --git a/imgs/gui/settings_menu.png b/imgs/gui/settings_menu.png new file mode 100644 index 000000000..c1ccc619d Binary files /dev/null and b/imgs/gui/settings_menu.png differ diff --git a/imgs/gui/settings_stats.png b/imgs/gui/settings_stats.png new file mode 100644 index 000000000..088461479 Binary files /dev/null and b/imgs/gui/settings_stats.png differ diff --git a/pyfa.py b/pyfa.py index 05b265024..561b1fd80 100755 --- a/pyfa.py +++ b/pyfa.py @@ -26,6 +26,7 @@ import config from optparse import OptionParser, BadOptionError, AmbiguousOptionError +import logbook from logbook import TimedRotatingFileHandler, Logger, StreamHandler, NestedSetup, FingersCrossedHandler, NullHandler, \ CRITICAL, ERROR, WARNING, DEBUG, INFO pyfalog = Logger(__name__) @@ -144,6 +145,10 @@ if not hasattr(sys, 'frozen'): print("Cannot find python-dateutil.\nYou can download python-dateutil from https://pypi.python.org/pypi/python-dateutil") sys.exit(1) + logVersion = logbook.__version__.split('.') + if int(logVersion[0]) < 1: + print ("Logbook version >= 1.0.0 is recommended. You may have some performance issues by continuing to use an earlier version.") + if __name__ == "__main__": # Configure paths diff --git a/requirements.txt b/requirements.txt index ed301fe25..d3bc33dde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -logbook +logbook>=1.0.0 matplotlib PyYAML python-dateutil diff --git a/service/character.py b/service/character.py index 4eea8e54b..1e5185b69 100644 --- a/service/character.py +++ b/service/character.py @@ -194,11 +194,13 @@ class Character(object): @staticmethod def backupSkills(path, saveFmt, activeFit, callback): thread = SkillBackupThread(path, saveFmt, activeFit, callback) + pyfalog.debug("Starting backup skills thread.") thread.start() @staticmethod def importCharacter(path, callback): thread = CharacterImportThread(path, callback) + pyfalog.debug("Starting import character thread.") thread.start() @staticmethod diff --git a/service/fit.py b/service/fit.py index 727ba913f..c1ce66f45 100644 --- a/service/fit.py +++ b/service/fit.py @@ -19,6 +19,7 @@ import copy from logbook import Logger +from time import time import eos.db from eos.saveddata.booster import Booster as es_Booster @@ -81,12 +82,14 @@ class Fit(object): @staticmethod def getAllFits(): + pyfalog.debug("Fetching all fits") fits = eos.db.getFitList() return fits @staticmethod def getFitsWithShip(shipID): """ Lists fits of shipID, used with shipBrowser """ + pyfalog.debug("Fetching all fits for ship ID: {0}", shipID) fits = eos.db.getFitsWithShip(shipID) names = [] for fit in fits: @@ -97,6 +100,7 @@ class Fit(object): @staticmethod def getBoosterFits(): """ Lists fits flagged as booster """ + pyfalog.debug("Fetching all fits flagged as a booster.") fits = eos.db.getBoosterFits() names = [] for fit in fits: @@ -106,10 +110,12 @@ class Fit(object): @staticmethod def countAllFits(): + pyfalog.debug("Getting count of all fits.") return eos.db.countAllFits() @staticmethod def countFitsWithShip(stuff): + pyfalog.debug("Getting count of all fits for: {0}", stuff) count = eos.db.countFitsWithShip(stuff) return count @@ -119,6 +125,7 @@ class Fit(object): return fit.modules[pos] def newFit(self, shipID, name=None): + pyfalog.debug("Creating new fit for ID: {0}", shipID) try: ship = es_Ship(eos.db.getItem(shipID)) except ValueError: @@ -135,18 +142,21 @@ class Fit(object): @staticmethod def toggleBoostFit(fitID): + pyfalog.debug("Toggling as booster for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) fit.booster = not fit.booster eos.db.commit() @staticmethod def renameFit(fitID, newName): + pyfalog.debug("Renaming fit ({0}) to: {1}", fitID, newName) fit = eos.db.getFit(fitID) fit.name = newName eos.db.commit() @staticmethod def deleteFit(fitID): + pyfalog.debug("Deleting fit for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) eos.db.remove(fit) @@ -159,6 +169,7 @@ class Fit(object): @staticmethod def copyFit(fitID): + pyfalog.debug("Creating copy of fit ID: {0}", fitID) fit = eos.db.getFit(fitID) newFit = copy.deepcopy(fit) eos.db.save(newFit) @@ -166,6 +177,7 @@ class Fit(object): @staticmethod def clearFit(fitID): + pyfalog.debug("Clearing fit for fit ID: {0}", fitID) if fitID is None: return None @@ -174,6 +186,7 @@ class Fit(object): return fit def toggleFactorReload(self, fitID): + pyfalog.debug("Toggling factor reload for fit ID: {0}", fitID) if fitID is None: return None @@ -183,6 +196,7 @@ class Fit(object): self.recalc(fit) def switchFit(self, fitID): + pyfalog.debug("Switching fit to fit ID: {0}", fitID) if fitID is None: return None @@ -206,6 +220,7 @@ class Fit(object): Projected is a recursion flag that is set to reduce recursions into projected fits Basic is a flag to simply return the fit without any other processing """ + pyfalog.debug("Getting fit for fit ID: {0}", fitID) if fitID is None: return None fit = eos.db.getFit(fitID) @@ -231,6 +246,7 @@ class Fit(object): @staticmethod def searchFits(name): + pyfalog.debug("Searching for fit: {0}", name) results = eos.db.searchFits(name) fits = [] for fit in results: @@ -240,6 +256,7 @@ class Fit(object): return fits def addImplant(self, fitID, itemID, recalc=True): + pyfalog.debug("Adding implant to fit ({0}) for item ID: {1}", fitID, itemID) if fitID is None: return False @@ -248,6 +265,7 @@ class Fit(object): try: implant = es_Implant(item) except ValueError: + pyfalog.warning("Invalid item: {0}", itemID) return False fit.implants.append(implant) @@ -256,6 +274,7 @@ class Fit(object): return True def removeImplant(self, fitID, position): + pyfalog.debug("Removing implant from position ({0}) for fit ID: {1}", position, fitID) if fitID is None: return False @@ -266,6 +285,7 @@ class Fit(object): return True def addBooster(self, fitID, itemID): + pyfalog.debug("Adding booster ({0}) to fit ID: {1}", itemID, fitID) if fitID is None: return False @@ -274,6 +294,7 @@ class Fit(object): try: booster = es_Booster(item) except ValueError: + pyfalog.warning("Invalid item: {0}", itemID) return False fit.boosters.append(booster) @@ -281,6 +302,7 @@ class Fit(object): return True def removeBooster(self, fitID, position): + pyfalog.debug("Removing booster from position ({0}) for fit ID: {1}", position, fitID) if fitID is None: return False @@ -291,6 +313,7 @@ class Fit(object): return True def project(self, fitID, thing): + pyfalog.debug("Projecting fit ({0}) onto: {1}", fitID, thing) if fitID is None: return @@ -340,6 +363,7 @@ class Fit(object): return True def addCommandFit(self, fitID, thing): + pyfalog.debug("Projecting command fit ({0}) onto: {1}", fitID, thing) if fitID is None: return @@ -359,6 +383,7 @@ class Fit(object): return True def toggleProjected(self, fitID, thing, click): + pyfalog.debug("Toggling projected on fit ({0}) for: {1}", fitID, thing) fit = eos.db.getFit(fitID) if isinstance(thing, es_Drone): if thing.amountActive == 0 and thing.canBeApplied(fit): @@ -380,6 +405,7 @@ class Fit(object): self.recalc(fit) def toggleCommandFit(self, fitID, thing): + pyfalog.debug("Toggle command fit ({0}) for: {1}", fitID, thing) fit = eos.db.getFit(fitID) commandInfo = thing.getCommandInfo(fitID) if commandInfo: @@ -390,6 +416,7 @@ class Fit(object): def changeAmount(self, fitID, projected_fit, amount): """Change amount of projected fits""" + pyfalog.debug("Changing fit ({0}) for projected fit ({1}) to new amount: {2}", fitID, projected_fit.getProjectionInfo(fitID), amount) fit = eos.db.getFit(fitID) amount = min(20, max(1, amount)) # 1 <= a <= 20 projectionInfo = projected_fit.getProjectionInfo(fitID) @@ -400,6 +427,7 @@ class Fit(object): self.recalc(fit) def changeActiveFighters(self, fitID, fighter, amount): + pyfalog.debug("Changing active fighters ({0}) for fit ({1}) to amount: {2}", fighter.itemID, amount) fit = eos.db.getFit(fitID) fighter.amountActive = amount @@ -407,6 +435,7 @@ class Fit(object): self.recalc(fit) def removeProjected(self, fitID, thing): + pyfalog.debug("Removing projection on fit ({0}) from: {1}", fitID, thing) fit = eos.db.getFit(fitID) if isinstance(thing, es_Drone): fit.projectedDrones.remove(thing) @@ -422,6 +451,7 @@ class Fit(object): self.recalc(fit) def removeCommand(self, fitID, thing): + pyfalog.debug("Removing command projection from fit ({0}) for: {1}", fitID, thing) fit = eos.db.getFit(fitID) del fit.__commandFits[thing.ID] @@ -429,11 +459,13 @@ class Fit(object): self.recalc(fit) def appendModule(self, fitID, itemID): + pyfalog.debug("Appending module for fit ({0}) using item: {1}", fitID, itemID) fit = eos.db.getFit(fitID) item = eos.db.getItem(itemID, eager=("attributes", "group.category")) try: m = es_Module(item) except ValueError: + pyfalog.warning("Invalid item: {0}", itemID) return False if m.item.category.name == "Subsystem": @@ -459,6 +491,7 @@ class Fit(object): return None def removeModule(self, fitID, position): + pyfalog.debug("Removing module from position ({0}) for fit ID: {1}", position, fitID) fit = eos.db.getFit(fitID) if fit.modules[position].isEmpty: return None @@ -472,6 +505,7 @@ class Fit(object): return numSlots != len(fit.modules) def changeModule(self, fitID, position, newItemID): + pyfalog.debug("Changing position of module from position ({0}) for fit ID: {1}", position, fitID) fit = eos.db.getFit(fitID) # Dummy it out in case the next bit fails @@ -481,6 +515,7 @@ class Fit(object): try: m = es_Module(item) except ValueError: + pyfalog.warning("Invalid item: {0}", newItemID) return False if m.fits(fit): @@ -509,6 +544,7 @@ class Fit(object): sanity checks as opposed to the GUI View. This is different than how the normal .swapModules() does things, which is mostly a blind swap. """ + pyfalog.debug("Moving cargo item to module for fit ID: {1}", fitID) fit = eos.db.getFit(fitID) module = fit.modules[moduleIdx] @@ -521,6 +557,7 @@ class Fit(object): if cargoP.isValidState(State.ACTIVE): cargoP.state = State.ACTIVE except: + pyfalog.warning("Invalid item: {0}", cargo.item) return if cargoP.slot != module.slot: # can't swap modules to different racks @@ -555,6 +592,7 @@ class Fit(object): @staticmethod def swapModules(fitID, src, dst): + pyfalog.debug("Swapping modules from source ({0}) to destination ({1}) for fit ID: {1}", src, dst, fitID) fit = eos.db.getFit(fitID) # Gather modules srcMod = fit.modules[src] @@ -574,6 +612,7 @@ class Fit(object): This will overwrite dst! Checking for empty module must be done at a higher level """ + pyfalog.debug("Cloning modules from source ({0}) to destination ({1}) for fit ID: {1}", src, dst, fitID) fit = eos.db.getFit(fitID) # Gather modules srcMod = fit.modules[src] @@ -594,6 +633,7 @@ class Fit(object): Adds cargo via typeID of item. If replace = True, we replace amount with given parameter, otherwise we increment """ + pyfalog.debug("Adding cargo ({0}) fit ID: {1}", itemID, fitID) if fitID is None: return False @@ -626,6 +666,7 @@ class Fit(object): return True def removeCargo(self, fitID, position): + pyfalog.debug("Removing cargo from position ({0}) fit ID: {1}", position, fitID) if fitID is None: return False @@ -636,6 +677,7 @@ class Fit(object): return True def addFighter(self, fitID, itemID): + pyfalog.debug("Adding fighters ({0}) to fit ID: {1}", itemID, fitID) if fitID is None: return False @@ -682,6 +724,7 @@ class Fit(object): return False def removeFighter(self, fitID, i): + pyfalog.debug("Removing fighters from fit ID: {0}", fitID) fit = eos.db.getFit(fitID) f = fit.fighters[i] fit.fighters.remove(f) @@ -691,6 +734,7 @@ class Fit(object): return True def addDrone(self, fitID, itemID, numDronesToAdd=1): + pyfalog.debug("Adding {0} drones ({1}) to fit ID: {2}", numDronesToAdd, itemID, fitID) if fitID is None: return False @@ -717,6 +761,7 @@ class Fit(object): return False def mergeDrones(self, fitID, d1, d2, projected=False): + pyfalog.debug("Merging drones on fit ID: {0}", fitID) if fitID is None: return False @@ -743,6 +788,7 @@ class Fit(object): @staticmethod def splitDrones(fit, d, amount, l): + pyfalog.debug("Splitting drones for fit ID: {0}", fit) total = d.amount active = d.amountActive > 0 d.amount = amount @@ -755,6 +801,7 @@ class Fit(object): eos.db.commit() def splitProjectedDroneStack(self, fitID, d, amount): + pyfalog.debug("Splitting projected drone stack for fit ID: {0}", fitID) if fitID is None: return False @@ -762,6 +809,7 @@ class Fit(object): self.splitDrones(fit, d, amount, fit.projectedDrones) def splitDroneStack(self, fitID, d, amount): + pyfalog.debug("Splitting drone stack for fit ID: {0}", fitID) if fitID is None: return False @@ -769,6 +817,7 @@ class Fit(object): self.splitDrones(fit, d, amount, fit.drones) def removeDrone(self, fitID, i, numDronesToRemove=1): + pyfalog.debug("Removing {0} drones for fit ID: {1}", numDronesToRemove, fitID) fit = eos.db.getFit(fitID) d = fit.drones[i] d.amount -= numDronesToRemove @@ -783,6 +832,7 @@ class Fit(object): return True def toggleDrone(self, fitID, i): + pyfalog.debug("Toggling drones for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) d = fit.drones[i] if d.amount == d.amountActive: @@ -795,6 +845,7 @@ class Fit(object): return True def toggleFighter(self, fitID, i): + pyfalog.debug("Toggling fighters for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) f = fit.fighters[i] f.active = not f.active @@ -804,6 +855,7 @@ class Fit(object): return True def toggleImplant(self, fitID, i): + pyfalog.debug("Toggling implant for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) implant = fit.implants[i] implant.active = not implant.active @@ -813,6 +865,7 @@ class Fit(object): return True def toggleImplantSource(self, fitID, source): + pyfalog.debug("Toggling implant source for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) fit.implantSource = source @@ -821,6 +874,7 @@ class Fit(object): return True def toggleBooster(self, fitID, i): + pyfalog.debug("Toggling booster for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) booster = fit.boosters[i] booster.active = not booster.active @@ -830,12 +884,14 @@ class Fit(object): return True def toggleFighterAbility(self, fitID, ability): + pyfalog.debug("Toggling fighter ability for fit ID: {0}", fitID) fit = eos.db.getFit(fitID) ability.active = not ability.active eos.db.commit() self.recalc(fit) def changeChar(self, fitID, charID): + pyfalog.debug("Changing character ({0}) for fit ID: {1}", charID, fitID) if fitID is None or charID is None: if charID is not None: self.character = Character.getInstance().all5() @@ -851,6 +907,7 @@ class Fit(object): return eos.db.getItem(itemID).category.name == "Charge" def setAmmo(self, fitID, ammoID, modules): + pyfalog.debug("Set ammo for fit ID: {0}", fitID) if fitID is None: return @@ -865,6 +922,7 @@ class Fit(object): @staticmethod def getTargetResists(fitID): + pyfalog.debug("Get target resists for fit ID: {0}", fitID) if fitID is None: return @@ -872,6 +930,7 @@ class Fit(object): return fit.targetResists def setTargetResists(self, fitID, pattern): + pyfalog.debug("Set target resist for fit ID: {0}", fitID) if fitID is None: return @@ -883,6 +942,7 @@ class Fit(object): @staticmethod def getDamagePattern(fitID): + pyfalog.debug("Get damage pattern for fit ID: {0}", fitID) if fitID is None: return @@ -890,6 +950,7 @@ class Fit(object): return fit.damagePattern def setDamagePattern(self, fitID, pattern): + pyfalog.debug("Set damage pattern for fit ID: {0}", fitID) if fitID is None: return @@ -900,6 +961,7 @@ class Fit(object): self.recalc(fit) def setMode(self, fitID, mode): + pyfalog.debug("Set mode for fit ID: {0}", fitID) if fitID is None: return @@ -910,6 +972,7 @@ class Fit(object): self.recalc(fit) def setAsPattern(self, fitID, ammo): + pyfalog.debug("Set as pattern for fit ID: {0}", fitID) if fitID is None: return @@ -927,6 +990,7 @@ class Fit(object): self.recalc(fit) def checkStates(self, fit, base): + pyfalog.debug("Check states for fit ID: {0}", fit) changed = False for mod in fit.modules: if mod != base: @@ -951,6 +1015,7 @@ class Fit(object): self.recalc(fit) def toggleModulesState(self, fitID, base, modules, click): + pyfalog.debug("Toggle module state for fit ID: {0}", fitID) changed = False proposedState = self.__getProposedState(base, click) @@ -990,6 +1055,7 @@ class Fit(object): State.ONLINE: State.OFFLINE} def __getProposedState(self, mod, click, proposedState=None): + pyfalog.debug("Get proposed state for module.") if mod.slot == Slot.SUBSYSTEM or mod.isEmpty: return State.ONLINE @@ -1017,6 +1083,7 @@ class Fit(object): return currState def refreshFit(self, fitID): + pyfalog.debug("Refresh fit for fit ID: {0}", fitID) if fitID is None: return None @@ -1025,9 +1092,12 @@ class Fit(object): self.recalc(fit) def recalc(self, fit, withBoosters=True): + start_time = time() pyfalog.info("=" * 10 + "recalc" + "=" * 10) if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]: fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"] fit.clear() fit.calculateModifiedAttributes(withBoosters=False) + + pyfalog.info("=" * 10 + "recalc time: " + str(time() - start_time) + "=" * 10) diff --git a/service/market.py b/service/market.py index e311c94e5..70dcd1e71 100644 --- a/service/market.py +++ b/service/market.py @@ -229,6 +229,7 @@ class Market(object): "Apotheosis" : self.les_grp, # 5th EVE anniversary present "Zephyr" : self.les_grp, # 2010 new year gift "Primae" : self.les_grp, # Promotion of planetary interaction + "Council Diplomatic Shuttle" : self.les_grp, # CSM X celebration "Freki" : self.les_grp, # AT7 prize "Mimir" : self.les_grp, # AT7 prize "Utu" : self.les_grp, # AT8 prize @@ -274,7 +275,6 @@ class Market(object): "Guristas Shuttle" : False, "Mobile Decoy Unit" : False, # Seems to be left over test mod for deployables "Tournament Micro Jump Unit" : False, # Normally seen only on tournament arenas - "Council Diplomatic Shuttle" : False, # CSM X celebration "Civilian Gatling Railgun" : True, "Civilian Gatling Pulse Laser" : True, "Civilian Gatling Autocannon" : True, diff --git a/service/network.py b/service/network.py index d050e88e2..a990fee23 100644 --- a/service/network.py +++ b/service/network.py @@ -21,10 +21,13 @@ import urllib2 import urllib import socket +from logbook import Logger import config from service.settings import NetworkSettings +pyfalog = Logger(__name__) + # network timeout, otherwise pyfa hangs for a long while if no internet connection timeout = 3 socket.setdefaulttimeout(timeout) @@ -76,6 +79,7 @@ class Network(object): access = NetworkSettings.getInstance().getAccess() if not self.ENABLED & access or not type & access: + pyfalog.warning("Access not enabled - please enable in Preferences > Network") raise Error("Access not enabled - please enable in Preferences > Network") # Set up some things for the request @@ -113,6 +117,8 @@ class Network(object): try: return urllib2.urlopen(request) except urllib2.HTTPError as error: + pyfalog.warning("HTTPError:") + pyfalog.warning(error) if error.code == 404: raise RequestError() elif error.code == 403: @@ -120,6 +126,8 @@ class Network(object): elif error.code >= 500: raise ServerError() except urllib2.URLError as error: + pyfalog.warning("Timed out or other URL error:") + pyfalog.warning(error) if "timed out" in error.reason: raise TimeoutError() else: diff --git a/service/port.py b/service/port.py index 31d1a0b94..3e923ed0f 100644 --- a/service/port.py +++ b/service/port.py @@ -83,11 +83,13 @@ class Port(object): @staticmethod def backupFits(path, callback): + pyfalog.debug("Starting backup fits thread.") thread = FitBackupThread(path, callback) thread.start() @staticmethod def importFitsThreaded(paths, callback): + pyfalog.debug("Starting import fits thread.") thread = FitImportThread(paths, callback) thread.start() @@ -105,12 +107,14 @@ class Port(object): fits = [] for path in paths: if callback: # Pulse + pyfalog.debug("Processing file:\n{0}", path) wx.CallAfter(callback, 1, "Processing file:\n%s" % path) file_ = open(path, "r") srcString = file_.read() if len(srcString) == 0: # ignore blank files + pyfalog.debug("File is blank.") continue codec_found = None @@ -165,6 +169,7 @@ class Port(object): _, fitsImport = Port.importAuto(srcString, path, callback=callback, encoding=codec_found) fits += fitsImport except xml.parsers.expat.ExpatError: + pyfalog.warning("Malformed XML in:\n{0}", path) return False, "Malformed XML in %s" % path except Exception as e: pyfalog.critical("Unknown exception processing: {0}", path) @@ -181,6 +186,7 @@ class Port(object): db.save(fit) IDs.append(fit.ID) if callback: # Pulse + pyfalog.debug("Processing complete, saving fits to database: {0}/{1}", i + 1, numFits) wx.CallAfter( callback, 1, "Processing complete, saving fits to database\n(%d/%d)" % @@ -612,7 +618,7 @@ class Port(object): if m.fits(fit): m.owner = fit if not m.isValidState(m.state): - print("Error: Module", m, "cannot have state", m.state) + pyfalog.warning("Error: Module {0} cannot have state {1}", m, m.state) fit.modules.append(m) @@ -810,8 +816,9 @@ class Port(object): if callback: wx.CallAfter(callback, None) # Skip fit silently if we get an exception - except Exception: + except Exception as e: pyfalog.error("Caught exception on fit.") + pyfalog.error(e) pass return fits @@ -835,8 +842,9 @@ class Port(object): f.ship = Ship(sMkt.getItem(shipType)) except ValueError: f.ship = Citadel(sMkt.getItem(shipType)) - except: + except Exception as e: pyfalog.warning("Caught exception on importXml") + pyfalog.error(e) continue hardwares = fitting.getElementsByTagName("hardware") moduleList = [] @@ -845,8 +853,9 @@ class Port(object): moduleName = hardware.getAttribute("type") try: item = sMkt.getItem(moduleName, eager="group.category") - except: + except Exception as e: pyfalog.warning("Caught exception on importXml") + pyfalog.error(e) continue if item: if item.category.name == "Drone": diff --git a/service/prefetch.py b/service/prefetch.py index e73da9ff5..2680b0184 100644 --- a/service/prefetch.py +++ b/service/prefetch.py @@ -59,6 +59,7 @@ if config.saveDB and os.path.isfile(config.saveDB): else: # If database does not exist, do not worry about migration. Simply # create and set version + pyfalog.debug("Existing database not found, creating new database.") db.saveddata_meta.create_all() db.saveddata_engine.execute('PRAGMA user_version = {}'.format(migration.getAppVersion())) # Import default database values diff --git a/service/price.py b/service/price.py index facb5e389..b2466c4c4 100644 --- a/service/price.py +++ b/service/price.py @@ -104,6 +104,7 @@ class Price(object): try: percprice = float(sell.getElementsByTagName("percentile").item(0).firstChild.data) except (TypeError, ValueError): + pyfalog.warning("Failed to get price for: {0}", type_) percprice = 0 # Fill price data diff --git a/service/server.py b/service/server.py index ff71d2f92..4d6566de9 100644 --- a/service/server.py +++ b/service/server.py @@ -82,11 +82,15 @@ class AuthHandler(BaseHTTPServer.BaseHTTPRequestHandler): try: if step2: self.server.callback(parts) + pyfalog.info("Successfully logged into CREST.") msg = "If you see this message then it means you should be logged into CREST. You may close this window and return to the application." else: # For implicit mode, we have to serve up the page which will take the hash and redirect useing a querystring + pyfalog.info("Processing response from EVE Online.") msg = "Processing response from EVE Online" except Exception, ex: + pyfalog.error("Error in CREST AuthHandler") + pyfalog.error(ex) msg = "
{}
".format(ex.message) finally: self.send_response(200) @@ -127,6 +131,7 @@ class StoppableHTTPServer(BaseHTTPServer.HTTPServer): pass def stop(self): + pyfalog.warning("Setting CREST server to stop.") self.run = False def handle_timeout(self): diff --git a/service/settings.py b/service/settings.py index 8c058e203..3da94b084 100644 --- a/service/settings.py +++ b/service/settings.py @@ -22,13 +22,15 @@ import os.path import urllib2 import config +import eos.config from logbook import Logger pyfalog = Logger(__name__) class SettingsProvider(object): - BASE_PATH = os.path.join(config.savePath, 'settings') + if config.savePath: + BASE_PATH = os.path.join(config.savePath, 'settings') settings = {} _instance = None @@ -40,13 +42,15 @@ class SettingsProvider(object): return cls._instance def __init__(self): - if not os.path.exists(self.BASE_PATH): - os.mkdir(self.BASE_PATH) + if hasattr(self, 'BASE_PATH'): + if not os.path.exists(self.BASE_PATH): + os.mkdir(self.BASE_PATH) def getSettings(self, area, defaults=None): s = self.settings.get(area) - if s is None: + + if s is None and hasattr(self, 'BASE_PATH'): p = os.path.join(self.BASE_PATH, area) if not os.path.exists(p): @@ -370,7 +374,8 @@ class StatViewSettings(object): "targetingMisc": 1, "price" : 2, "miningyield" : 2, - "drones" : 2 + "drones" : 2, + "outgoing" : 2, } # We don't have these....yet @@ -406,14 +411,17 @@ class ContextMenuSettings(object): "ammoPattern" : 1, "amount" : 1, "cargo" : 1, + "cargoAmmo" : 1, "changeAffectingSkills" : 1, "damagePattern" : 1, "droneRemoveStack" : 1, "droneSplit" : 1, + "droneStack" : 1, "factorReload" : 1, "fighterAbilities" : 1, - "implantSet" : 1, + "implantSets" : 1, "itemStats" : 1, + "itemRemove" : 1, "marketJump" : 1, "metaSwap" : 1, "moduleAmmoPicker" : 1, @@ -435,4 +443,24 @@ class ContextMenuSettings(object): def set(self, type, value): self.ContextMenuDefaultSettings[type] = value + +class EOSSettings(object): + _instance = None + + @classmethod + def getInstance(cls): + if cls._instance is None: + cls._instance = EOSSettings() + + return cls._instance + + def __init__(self): + self.EOSSettings = SettingsProvider.getInstance().getSettings("pyfaEOSSettings", eos.config.settings) + + def get(self, type): + return self.EOSSettings[type] + + def set(self, type, value): + self.EOSSettings[type] = value + # @todo: migrate fit settings (from fit service) here? diff --git a/service/update.py b/service/update.py index 2cffd9cec..7196b2a59 100644 --- a/service/update.py +++ b/service/update.py @@ -85,8 +85,9 @@ class CheckUpdateThread(threading.Thread): if release['prerelease'] and rVersion > config.expansionVersion: wx.CallAfter(self.callback, release) # Singularity -> Singularity break - except: - pyfalog.warning("Caught exception in run") + except Exception as e: + pyfalog.error("Caught exception in run") + pyfalog.error(e) pass @staticmethod @@ -100,6 +101,7 @@ class Update(object): @staticmethod def CheckUpdate(callback): thread = CheckUpdateThread(callback) + pyfalog.debug("Starting Check Update Thread.") thread.start() @classmethod diff --git a/tests/test_modules/gui/test_aboutData.py b/tests/test_modules/gui/test_aboutData.py deleted file mode 100644 index 8e7e862d6..000000000 --- a/tests/test_modules/gui/test_aboutData.py +++ /dev/null @@ -1,9 +0,0 @@ -from gui.aboutData import versionString, licenses, developers, credits, description - - -def test_aboutData(): - assert versionString.__len__() > 0 - assert licenses.__len__() > 0 - assert developers.__len__() > 0 - assert credits.__len__() > 0 - assert description.__len__() > 0 diff --git a/tests/test_modules/test_eos/test_gamedata.py b/tests/test_modules/test_eos/test_gamedata.py new file mode 100644 index 000000000..4a8674339 --- /dev/null +++ b/tests/test_modules/test_eos/test_gamedata.py @@ -0,0 +1,17 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +import os +import sys +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..'))) + +# noinspection PyPackageRequirements +from _development.helpers import DBInMemory as DB, Gamedata, Saveddata +from _development.helpers_fits import RifterFit, KeepstarFit + +def test_race(DB, RifterFit, KeepstarFit): + """ + Test race code + """ + assert RifterFit.ship.item.race == 'minmatar' + assert KeepstarFit.ship.item.race == 'upwell' diff --git a/tests/test_modules/test_eos/test_modifiedAttributeDict.py b/tests/test_modules/test_eos/test_modifiedAttributeDict.py new file mode 100644 index 000000000..55b68fb14 --- /dev/null +++ b/tests/test_modules/test_eos/test_modifiedAttributeDict.py @@ -0,0 +1,50 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +import math +import os +import sys +script_dir = os.path.dirname(os.path.abspath(__file__)) +script_dir = os.path.realpath(os.path.join(script_dir, '..', '..', '..')) +print script_dir +sys.path.append(script_dir) + +# noinspection PyPackageRequirements +from _development.helpers import DBInMemory as DB, Gamedata, Saveddata +from _development.helpers_fits import RifterFit + +def test_multiply_stacking_penalties(DB, Saveddata, RifterFit): + """ + Tests the stacking penalties under multiply + """ + char0 = Saveddata['Character'].getAll0() + + RifterFit.character = char0 + starting_em_resist = RifterFit.ship.getModifiedItemAttr("shieldEmDamageResonance") + + mod = Saveddata['Module'](DB['db'].getItem("EM Ward Amplifier II")) + item_modifer = mod.item.getAttribute("emDamageResistanceBonus") + + RifterFit.calculateModifiedAttributes() + + for _ in range(10): + if _ == 0: + # First run we have no modules, se don't try and calculate them. + calculated_resist = RifterFit.ship.getModifiedItemAttr("shieldEmDamageResonance") + else: + # Calculate what our next resist should be + # Denominator: [math.exp((i / 2.67) ** 2.0) for i in xrange(8)] + current_effectiveness = 1 / math.exp(((_ - 1) / 2.67) ** 2.0) + new_item_modifier = 1 + ((item_modifer * current_effectiveness) / 100) + calculated_resist = (em_resist * new_item_modifier) + + # Add another resist module to our fit. + RifterFit.modules.append(mod) + + # Modify our fit so that Eos generates new numbers for us. + RifterFit.clear() + RifterFit.calculateModifiedAttributes() + + em_resist = RifterFit.ship.getModifiedItemAttr("shieldEmDamageResonance") + + assert em_resist == calculated_resist + # print(str(em_resist) + "==" + str(calculated_resist)) diff --git a/tests/test_modules/test_gui/test_aboutData.py b/tests/test_modules/test_gui/test_aboutData.py new file mode 100644 index 000000000..b17668e68 --- /dev/null +++ b/tests/test_modules/test_gui/test_aboutData.py @@ -0,0 +1,19 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +import os +import sys +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..'))) + +from gui.aboutData import versionString, licenses, developers, credits, description + + +def test_aboutData(): + """ + Simple test to validate all about data exists + """ + assert versionString.__len__() > 0 + assert licenses.__len__() > 0 + assert developers.__len__() > 0 + assert credits.__len__() > 0 + assert description.__len__() > 0 diff --git a/tests/test_modules/service/test_attribute.py b/tests/test_modules/test_service/test_attribute.py similarity index 82% rename from tests/test_modules/service/test_attribute.py rename to tests/test_modules/test_service/test_attribute.py index 1ab44f2a8..0f9622885 100644 --- a/tests/test_modules/service/test_attribute.py +++ b/tests/test_modules/test_service/test_attribute.py @@ -1,9 +1,16 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +import os +import sys +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..'))) + from service.attribute import Attribute def test_attribute(): """ - We don't really have much to test here, to throw a generic attribute at it and validate we get the expected results + We don't really have much to test here, so throw a generic attribute at it and validate we get the expected results :return: """ diff --git a/tests/test_modules/test_service/test_fit.py b/tests/test_modules/test_service/test_fit.py new file mode 100644 index 000000000..dff83eb49 --- /dev/null +++ b/tests/test_modules/test_service/test_fit.py @@ -0,0 +1,37 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +# import os +# import sys +# script_dir = os.path.dirname(os.path.abspath(__file__)) +# sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..', '..'))) +# +# # noinspection PyPackageRequirements +# from _development.helpers import DBInMemory as DB, Gamedata, Saveddata +# # noinspection PyPackageRequirements +# from _development.helpers_fits import RifterFit, KeepstarFit +# from service.fit import Fit +# +# # Fake import wx +# # todo: fix this +# # from types import ModuleType +# # wx = ModuleType("fake_module") +# # sys.modules[wx.__name__] = wx +# +# def test_getAllFits(DB, RifterFit, KeepstarFit): +# assert len(Fit.getAllFits()) == 0 +# DB['db'].save(RifterFit) +# assert len(Fit.getAllFits()) == 1 +# DB['db'].save(KeepstarFit) +# assert len(Fit.getAllFits()) == 2 +# +# # Cleanup after ourselves +# DB['db'].remove(RifterFit) +# DB['db'].remove(KeepstarFit) +# +# +# def test_getFitsWithShip_RifterFit(DB, RifterFit): +# DB['db'].save(RifterFit) +# +# assert Fit.getFitsWithShip(587)[0][1] == 'My Rifter Fit' +# +# DB['db'].remove(RifterFit) diff --git a/tests/test_package.py b/tests/test_package.py deleted file mode 100644 index 5f97967d7..000000000 --- a/tests/test_package.py +++ /dev/null @@ -1,58 +0,0 @@ -"""import tests.""" - -import os -import sys -# import importlib - -# noinspection PyPackageRequirements -# import pytest - - -script_dir = os.path.dirname(os.path.abspath(__file__)) -# Add root to python paths, this allows us to import submodules -sys.path.append(os.path.realpath(os.path.join(script_dir, '..'))) - -# noinspection PyPep8 -import service -# noinspection PyPep8 -import gui -# noinspection PyPep8 -import eos -# noinspection PyPep8 -import utils - - -def test_packages(): - assert service - assert gui - assert eos - assert utils - - -def service_modules(): - for root, folders, files in os.walk("service"): - for file_ in files: - if file_.endswith(".py") and not file_.startswith("_"): - mod_name = "{}.{}".format( - root.replace("/", "."), - file_.split(".py")[0], - ) - yield mod_name - - -def eos_modules(): - for root, folders, files in os.walk("eos"): - for file_ in files: - if file_.endswith(".py") and not file_.startswith("_"): - mod_name = "{}.{}".format( - root.replace("/", "."), - file_.split(".py")[0], - ) - yield mod_name - -# TODO: Disable walk through Eos paths until eos.types is killed. eos.types causes the import to break -''' -@pytest.mark.parametrize("mod_name", eos_modules()) -def test_eos_imports(mod_name): - assert importlib.import_module(mod_name) -''' diff --git a/tests/test_smoketests/test_rifter.py b/tests/test_smoketests/test_rifter.py new file mode 100644 index 000000000..b6bb63ca5 --- /dev/null +++ b/tests/test_smoketests/test_rifter.py @@ -0,0 +1,235 @@ +# Add root folder to python paths +# This must be done on every test in order to pass in Travis +import os +import sys +script_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.realpath(os.path.join(script_dir, '..', '..'))) + +# noinspection PyPackageRequirements +from _development.helpers import DBInMemory as DB, Gamedata, Saveddata +from _development.helpers_fits import RifterFit + + +# noinspection PyShadowingNames +def test_rifter_empty_char0(DB, Saveddata, RifterFit): + """ + We test an empty ship because if we use this as a base for testing our V skills, + and CCP ever fucks with the base states, all our derived stats will be wrong. + """ + char0 = Saveddata['Character'].getAll0() + + RifterFit.character = char0 + RifterFit.calculateModifiedAttributes() + + assert RifterFit.ship.getModifiedItemAttr("agility") == 3.2 + assert RifterFit.ship.getModifiedItemAttr("armorEmDamageResonance") == 0.4 + assert RifterFit.ship.getModifiedItemAttr("armorExplosiveDamageResonance") == 0.9 + assert RifterFit.ship.getModifiedItemAttr("armorHP") == 450.0 + assert RifterFit.ship.getModifiedItemAttr("armorKineticDamageResonance") == 0.75 + assert RifterFit.ship.getModifiedItemAttr("armorThermalDamageResonance") == 0.65 + assert RifterFit.ship.getModifiedItemAttr("armorUniformity") == 0.75 + assert RifterFit.ship.getModifiedItemAttr("baseWarpSpeed") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("capacitorCapacity") == 250.0 + assert RifterFit.ship.getModifiedItemAttr("capacity") == 140.0 + assert RifterFit.ship.getModifiedItemAttr("cpuLoad") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 130.0 + assert RifterFit.ship.getModifiedItemAttr("damage") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("droneBandwidth") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("droneCapacity") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("emDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("explosiveDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("fwLpKill") == 25.0 + assert RifterFit.ship.getModifiedItemAttr("gfxBoosterID") == 397.0 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationHi") == 0.63 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationLow") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationMed") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityHi") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityLow") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityMed") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateHi") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateLow") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateMed") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatGenerationMultiplier") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hiSlots") == 4.0 + assert RifterFit.ship.getModifiedItemAttr("hp") == 350.0 + assert RifterFit.ship.getModifiedItemAttr("hullEmDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullExplosiveDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullKineticDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullThermalDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("kineticDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("launcherSlotsLeft") == 2.0 + assert RifterFit.ship.getModifiedItemAttr("lowSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("mainColor") == 16777215.0 + assert RifterFit.ship.getModifiedItemAttr("mass") == 1067000.0 + assert RifterFit.ship.getModifiedItemAttr("maxDirectionalVelocity") == 3000.0 + assert RifterFit.ship.getModifiedItemAttr("maxLockedTargets") == 4.0 + assert RifterFit.ship.getModifiedItemAttr("maxPassengers") == 2.0 + assert RifterFit.ship.getModifiedItemAttr("maxTargetRange") == 22500.0 + assert RifterFit.ship.getModifiedItemAttr("maxVelocity") == 365.0 + assert RifterFit.ship.getModifiedItemAttr("medSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("metaLevel") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("minTargetVelDmgMultiplier") == 0.05 + assert RifterFit.ship.getModifiedItemAttr("powerLoad") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("powerOutput") == 41.0 + assert RifterFit.ship.getModifiedItemAttr("powerToSpeed") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("propulsionGraphicID") == 397.0 + assert RifterFit.ship.getModifiedItemAttr("radius") == 31.0 + assert RifterFit.ship.getModifiedItemAttr("rechargeRate") == 125000.0 + assert RifterFit.ship.getModifiedItemAttr("requiredSkill1") == 3329.0 + assert RifterFit.ship.getModifiedItemAttr("requiredSkill1Level") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("rigSize") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("rigSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("scanGravimetricStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanLadarStrength") == 8.0 + assert RifterFit.ship.getModifiedItemAttr("scanMagnetometricStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanRadarStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanResolution") == 660.0 + assert RifterFit.ship.getModifiedItemAttr("scanSpeed") == 1500.0 + assert RifterFit.ship.getModifiedItemAttr("shieldCapacity") == 450.0 + assert RifterFit.ship.getModifiedItemAttr("shieldEmDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("shieldExplosiveDamageResonance") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("shieldKineticDamageResonance") == 0.6 + assert RifterFit.ship.getModifiedItemAttr("shieldRechargeRate") == 625000.0 + assert RifterFit.ship.getModifiedItemAttr("shieldThermalDamageResonance") == 0.8 + assert RifterFit.ship.getModifiedItemAttr("shieldUniformity") == 0.75 + assert RifterFit.ship.getModifiedItemAttr("shipBonusMF") == 5.0 + assert RifterFit.ship.getModifiedItemAttr("shipBonusMF2") == 10.0 + assert RifterFit.ship.getModifiedItemAttr("shipScanResistance") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("signatureRadius") == 35.0 + assert RifterFit.ship.getModifiedItemAttr("structureUniformity") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("techLevel") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("thermalDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("turretSlotsLeft") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("typeColorScheme") == 11342.0 + assert RifterFit.ship.getModifiedItemAttr("uniformity") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("upgradeCapacity") == 400.0 + assert RifterFit.ship.getModifiedItemAttr("upgradeSlotsLeft") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("volume") == 27289.0 + assert RifterFit.ship.getModifiedItemAttr("warpCapacitorNeed") == 2.24e-06 + assert RifterFit.ship.getModifiedItemAttr("warpFactor") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("warpSpeedMultiplier") == 5.0 + + +# noinspection PyShadowingNames +def test_rifter_empty_char5(DB, Saveddata, RifterFit): + """ + Test char skills applying to a ship + """ + char5 = Saveddata['Character'].getAll5() + + RifterFit.character = char5 + RifterFit.calculateModifiedAttributes() + + assert RifterFit.ship.getModifiedItemAttr("agility") == 2.16 + assert RifterFit.ship.getModifiedItemAttr("armorEmDamageResonance") == 0.4 + assert RifterFit.ship.getModifiedItemAttr("armorExplosiveDamageResonance") == 0.9 + assert RifterFit.ship.getModifiedItemAttr("armorHP") == 562.5 + assert RifterFit.ship.getModifiedItemAttr("armorKineticDamageResonance") == 0.75 + assert RifterFit.ship.getModifiedItemAttr("armorThermalDamageResonance") == 0.65 + assert RifterFit.ship.getModifiedItemAttr("armorUniformity") == 0.75 + assert RifterFit.ship.getModifiedItemAttr("baseWarpSpeed") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("capacitorCapacity") == 312.5 + assert RifterFit.ship.getModifiedItemAttr("capacity") == 140.0 + assert RifterFit.ship.getModifiedItemAttr("cpuLoad") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 162.5 + assert RifterFit.ship.getModifiedItemAttr("damage") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("droneBandwidth") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("droneCapacity") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("emDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("explosiveDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("fwLpKill") == 25.0 + assert RifterFit.ship.getModifiedItemAttr("gfxBoosterID") == 397.0 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationHi") == 0.63 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationLow") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("heatAttenuationMed") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityHi") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityLow") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatCapacityMed") == 100.0 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateHi") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateLow") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatDissipationRateMed") == 0.01 + assert RifterFit.ship.getModifiedItemAttr("heatGenerationMultiplier") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hiSlots") == 4.0 + assert RifterFit.ship.getModifiedItemAttr("hp") == 437.5 + assert RifterFit.ship.getModifiedItemAttr("hullEmDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullExplosiveDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullKineticDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("hullThermalDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("kineticDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("launcherSlotsLeft") == 2.0 + assert RifterFit.ship.getModifiedItemAttr("lowSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("mainColor") == 16777215.0 + assert RifterFit.ship.getModifiedItemAttr("mass") == 1067000.0 + assert RifterFit.ship.getModifiedItemAttr("maxDirectionalVelocity") == 3000.0 + assert RifterFit.ship.getModifiedItemAttr("maxLockedTargets") == 4.0 + assert RifterFit.ship.getModifiedItemAttr("maxPassengers") == 2.0 + assert RifterFit.ship.getModifiedItemAttr("maxTargetRange") == 28125.0 + assert RifterFit.ship.getModifiedItemAttr("maxVelocity") == 456.25 + assert RifterFit.ship.getModifiedItemAttr("medSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("metaLevel") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("minTargetVelDmgMultiplier") == 0.05 + assert RifterFit.ship.getModifiedItemAttr("powerLoad") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("powerOutput") == 51.25 + assert RifterFit.ship.getModifiedItemAttr("powerToSpeed") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("propulsionGraphicID") == 397.0 + assert RifterFit.ship.getModifiedItemAttr("radius") == 31.0 + assert RifterFit.ship.getModifiedItemAttr("rechargeRate") == 93750.0 + assert RifterFit.ship.getModifiedItemAttr("requiredSkill1") == 3329.0 + assert RifterFit.ship.getModifiedItemAttr("requiredSkill1Level") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("rigSize") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("rigSlots") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("scanGravimetricStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanLadarStrength") == 9.6 + assert RifterFit.ship.getModifiedItemAttr("scanMagnetometricStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanRadarStrength") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("scanResolution") == 825.0 + assert RifterFit.ship.getModifiedItemAttr("scanSpeed") == 1500.0 + assert RifterFit.ship.getModifiedItemAttr("shieldCapacity") == 562.5 + assert RifterFit.ship.getModifiedItemAttr("shieldEmDamageResonance") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("shieldExplosiveDamageResonance") == 0.5 + assert RifterFit.ship.getModifiedItemAttr("shieldKineticDamageResonance") == 0.6 + assert RifterFit.ship.getModifiedItemAttr("shieldRechargeRate") == 468750.0 + assert RifterFit.ship.getModifiedItemAttr("shieldThermalDamageResonance") == 0.8 + assert RifterFit.ship.getModifiedItemAttr("shieldUniformity") == 1 + assert RifterFit.ship.getModifiedItemAttr("shipBonusMF") == 5.0 + assert RifterFit.ship.getModifiedItemAttr("shipBonusMF2") == 10.0 + assert RifterFit.ship.getModifiedItemAttr("shipScanResistance") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("signatureRadius") == 35.0 + assert RifterFit.ship.getModifiedItemAttr("structureUniformity") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("techLevel") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("thermalDamageResonance") == 0.67 + assert RifterFit.ship.getModifiedItemAttr("turretSlotsLeft") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("typeColorScheme") == 11342.0 + assert RifterFit.ship.getModifiedItemAttr("uniformity") == 1.0 + assert RifterFit.ship.getModifiedItemAttr("upgradeCapacity") == 400.0 + assert RifterFit.ship.getModifiedItemAttr("upgradeSlotsLeft") == 3.0 + assert RifterFit.ship.getModifiedItemAttr("volume") == 27289.0 + assert RifterFit.ship.getModifiedItemAttr("warpCapacitorNeed") == 1.12e-06 + assert RifterFit.ship.getModifiedItemAttr("warpFactor") == 0.0 + assert RifterFit.ship.getModifiedItemAttr("warpSpeedMultiplier") == 5.0 + + +# noinspection PyShadowingNames +def test_rifter_coprocessor(DB, Saveddata, RifterFit): + char5 = Saveddata['Character'].getAll5() + char0 = Saveddata['Character'].getAll0() + + RifterFit.character = char0 + mod = Saveddata['Module'](DB['db'].getItem("Co-Processor II")) + mod.state = Saveddata['State'].OFFLINE + RifterFit.modules.append(mod) + + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 130 + + RifterFit.calculateModifiedAttributes() + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 130 + + mod.state = Saveddata['State'].ONLINE + RifterFit.clear() + RifterFit.calculateModifiedAttributes() + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 143 + + RifterFit.character = char5 + RifterFit.clear() + RifterFit.calculateModifiedAttributes() + assert RifterFit.ship.getModifiedItemAttr("cpuOutput") == 178.75