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 2c50fd001..884803d76 100644 --- a/config.py +++ b/config.py @@ -19,7 +19,7 @@ debug = False saveInRoot = False # Version data -version = "1.28.1" +version = "1.28.2" tag = "git" expansionName = "YC119.3" expansionVersion = "1.0" diff --git a/eos/config.py b/eos/config.py index 38371e299..9dc0eabbc 100644 --- a/eos/config.py +++ b/eos/config.py @@ -1,14 +1,25 @@ 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 diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index bac688769..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): 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/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index 0b64d9d53..496cf4fb8 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -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() @@ -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() 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/shipBrowser.py b/gui/shipBrowser.py index 33561de5b..c61f9b698 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -1746,6 +1746,7 @@ class FitItem(SFItem.SFBrowserItem): self.deleteFit() def deleteFit(self, event=None): + pyfalog.debug("Deleting ship fit.") if self.deleted: return else: 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/fit.py b/service/fit.py index 70a471042..0bb385894 100644 --- a/service/fit.py +++ b/service/fit.py @@ -79,12 +79,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: @@ -95,6 +97,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: @@ -104,10 +107,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 @@ -117,6 +122,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: @@ -133,18 +139,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) @@ -157,6 +166,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) @@ -164,6 +174,7 @@ class Fit(object): @staticmethod def clearFit(fitID): + pyfalog.debug("Clearing fit for fit ID: {0}", fitID) if fitID is None: return None @@ -172,6 +183,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 @@ -181,6 +193,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 @@ -204,6 +217,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) @@ -229,6 +243,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: @@ -238,6 +253,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 @@ -255,6 +271,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 @@ -265,6 +282,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 @@ -281,6 +299,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 +310,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 +360,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 +380,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 +402,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 +413,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 +424,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 +432,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 +448,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,6 +456,7 @@ 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: @@ -460,6 +488,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 @@ -473,6 +502,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 @@ -511,6 +541,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] @@ -558,6 +589,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] @@ -577,6 +609,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] @@ -597,6 +630,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 @@ -629,6 +663,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 @@ -639,6 +674,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 @@ -685,6 +721,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) @@ -694,6 +731,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 @@ -720,6 +758,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 @@ -746,6 +785,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 @@ -758,6 +798,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 @@ -765,6 +806,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 @@ -772,6 +814,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 @@ -786,6 +829,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: @@ -798,6 +842,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 @@ -807,6 +852,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 @@ -816,6 +862,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 @@ -824,6 +871,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 @@ -833,12 +881,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() @@ -854,6 +904,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 @@ -868,6 +919,7 @@ class Fit(object): @staticmethod def getTargetResists(fitID): + pyfalog.debug("Get target resists for fit ID: {0}", fitID) if fitID is None: return @@ -875,6 +927,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 @@ -886,6 +939,7 @@ class Fit(object): @staticmethod def getDamagePattern(fitID): + pyfalog.debug("Get damage pattern for fit ID: {0}", fitID) if fitID is None: return @@ -893,6 +947,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 @@ -903,6 +958,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 @@ -913,6 +969,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 @@ -930,6 +987,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: @@ -954,6 +1012,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) @@ -993,6 +1052,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 @@ -1020,6 +1080,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 diff --git a/service/settings.py b/service/settings.py index 5d44d4e88..69dc8130d 100644 --- a/service/settings.py +++ b/service/settings.py @@ -29,7 +29,8 @@ 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 @@ -41,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): @@ -71,6 +74,8 @@ class SettingsProvider(object): info[item] = defaults[item] self.settings[area] = s = Settings(p, info) + else: + s = None return s @@ -408,14 +413,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, 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..832a1a845 --- /dev/null +++ b/tests/test_modules/test_service/test_fit.py @@ -0,0 +1,36 @@ +# 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 +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