Merge branch 'development' into MorePreferences

This commit is contained in:
blitzman
2017-03-26 13:52:59 -04:00
58 changed files with 1032 additions and 367 deletions

View File

@@ -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).

0
_development/__init__.py Normal file
View File

143
_development/helpers.py Normal file
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()))

View File

@@ -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

View File

@@ -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')

View File

@@ -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

View File

@@ -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')

View File

@@ -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

BIN
eve.db

Binary file not shown.

View File

@@ -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"
]

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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,

View File

@@ -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,

View File

@@ -76,7 +76,7 @@ class PFGeneralPref(PreferenceView):
'''
def getImage(self):
return BitmapLoader.getBitmap("prefs_settings", "gui")
return BitmapLoader.getBitmap("settings_log", "gui")
PFGeneralPref.register()

View File

@@ -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()

View File

@@ -5,6 +5,7 @@ __all__ = [
"firepowerViewFull",
"capacitorViewFull",
"outgoingViewFull",
"outgoingViewMinimal",
"targetingMiscViewMinimal",
"priceViewFull",
]

View File

@@ -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"

View File

@@ -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()

View File

@@ -29,7 +29,7 @@ except ImportError:
class TargetingMiscViewFull(StatsView):
name = "targetingmiscViewFull"
name = "targetingMiscViewFull"
def __init__(self, parent):
StatsView.__init__(self)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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,

View File

@@ -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",

View File

@@ -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():

View File

@@ -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()

View File

@@ -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)

View File

@@ -40,7 +40,7 @@ class StatsPane(wx.Panel):
"resistances",
"recharge",
"firepower",
"outgoingView",
"outgoing",
"capacitor",
"targetingMisc",
"price",

View File

@@ -53,4 +53,5 @@ from gui.builtinStatsViews import ( # noqa: E402, F401
targetingMiscViewMinimal,
priceViewFull,
outgoingViewFull,
outgoingViewMinimal,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
imgs/gui/settings_log.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
imgs/gui/settings_menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
imgs/gui/settings_stats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

View File

@@ -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

View File

@@ -1,4 +1,4 @@
logbook
logbook>=1.0.0
matplotlib
PyYAML
python-dateutil

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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:

View File

@@ -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":

View File

@@ -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

View File

@@ -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

View File

@@ -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 = "<h2>Error</h2>\n<p>{}</p>".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):

View File

@@ -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?

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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))

View File

@@ -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

View File

@@ -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:
"""

View File

@@ -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)

View File

@@ -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)
'''

View File

@@ -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