diff --git a/eos/effects/dronemaxvelocitybonus.py b/eos/effects/dronemaxvelocitybonus.py
index 938b34fa0..e3fc1ba6e 100644
--- a/eos/effects/dronemaxvelocitybonus.py
+++ b/eos/effects/dronemaxvelocitybonus.py
@@ -8,4 +8,4 @@ type = "passive"
def handler(fit, container, context):
level = container.level if "skill" in context else 1
fit.drones.filteredItemBoost(lambda drone: drone.item.requiresSkill("Drones"),
- "maxVelocity", container.getModifiedItemAttr("droneMaxVelocityBonus") * level)
+ "maxVelocity", container.getModifiedItemAttr("droneMaxVelocityBonus") * level, stackingPenalties=True)
diff --git a/eos/effects/thermodynamicsskilldamagebonus.py b/eos/effects/thermodynamicsskilldamagebonus.py
index 54569b06a..0386ad6e6 100644
--- a/eos/effects/thermodynamicsskilldamagebonus.py
+++ b/eos/effects/thermodynamicsskilldamagebonus.py
@@ -6,5 +6,5 @@ type = "passive"
def handler(fit, skill, context):
- fit.modules.filteredItemBoost(lambda mod: True, "heatDamage",
+ fit.modules.filteredItemBoost(lambda mod: "heatDamage" in mod.item.attributes, "heatDamage",
skill.getModifiedItemAttr("thermodynamicsHeatDamage") * skill.level)
diff --git a/eos/saveddata/character.py b/eos/saveddata/character.py
index 85cd2645d..9316b32f7 100644
--- a/eos/saveddata/character.py
+++ b/eos/saveddata/character.py
@@ -57,8 +57,15 @@ class Character(object):
def init(self):
self.__skillIdMap = {}
+
for skill in self.__skills:
self.__skillIdMap[skill.itemID] = skill
+
+ # get a list of skills that the character does no have, and add them (removal of old skills happens in the
+ # Skill loading)
+ for skillID in set(self.getSkillIDMap().keys()).difference(set(self.__skillIdMap.keys())):
+ self.addSkill(Skill(self, skillID, self.defaultLevel))
+
self.dirtySkills = set()
self.alphaClone = None
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index e768bf216..d44f8adca 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -1016,6 +1016,16 @@ class Fit(object):
def getNumSlots(self, type):
return self.ship.getModifiedItemAttr(self.slots[type]) or 0
+ def getHardpointsFree(self, type):
+ if type == Hardpoint.NONE:
+ return 1
+ elif type == Hardpoint.TURRET:
+ return self.ship.getModifiedItemAttr('turretSlotsLeft') - self.getHardpointsUsed(Hardpoint.TURRET)
+ elif type == Hardpoint.MISSILE:
+ return self.ship.getModifiedItemAttr('launcherSlotsLeft') - self.getHardpointsUsed(Hardpoint.MISSILE)
+ else:
+ raise ValueError("%d is not a valid value for Hardpoint Enum", type)
+
@property
def calibrationUsed(self):
return self.getItemAttrOnlineSum(self.modules, 'upgradeCost')
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index 536be8614..ea4bac1a3 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -74,7 +74,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""An instance of this class represents a module together with its charge and modified attributes"""
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
MINING_ATTRIBUTES = ("miningAmount",)
- SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Uninteractable Localized Effect Beacon", "Non-Interactable Object")
+ SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
def __init__(self, item, baseItem=None, mutaplasmid=None):
"""Initialize a module from the program"""
@@ -527,7 +527,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
if max is not None:
current = 0 # if self.owner != fit else -1 # Disabled, see #1278
for mod in fit.modules:
- if mod.item and mod.item.groupID == self.item.groupID:
+ if (mod.item and mod.item.groupID == self.item.groupID and
+ self.modPosition != mod.modPosition):
current += 1
if current >= max:
@@ -535,12 +536,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
# Check this only if we're told to do so
if hardpointLimit:
- if self.hardpoint == Hardpoint.TURRET:
- if fit.ship.getModifiedItemAttr('turretSlotsLeft') - fit.getHardpointsUsed(Hardpoint.TURRET) < 1:
- return False
- elif self.hardpoint == Hardpoint.MISSILE:
- if fit.ship.getModifiedItemAttr('launcherSlotsLeft') - fit.getHardpointsUsed(Hardpoint.MISSILE) < 1:
- return False
+ if fit.getHardpointsFree(self.hardpoint) < 1:
+ return False
return True
diff --git a/gui/builtinContextMenus/whProjector.py b/gui/builtinContextMenus/whProjector.py
index 1ce95c906..ac0bd5c1b 100644
--- a/gui/builtinContextMenus/whProjector.py
+++ b/gui/builtinContextMenus/whProjector.py
@@ -195,7 +195,7 @@ class WhProjector(ContextMenu):
def getLocalizedEnvironments(self):
sMkt = Market.getInstance()
- grp = sMkt.getGroup("Uninteractable Localized Effect Beacon")
+ grp = sMkt.getGroup("Abyssal Hazards")
effects = dict()
diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py
index 36b3da6b1..d6bf4ae0c 100644
--- a/gui/builtinViews/fittingView.py
+++ b/gui/builtinViews/fittingView.py
@@ -686,7 +686,22 @@ class FittingView(d.Display):
# only consider changing color if we're dealing with a Module
if type(mod) is Module:
- if slotMap[mod.slot] or getattr(mod, 'restrictionOverridden', None): # Color too many modules as red
+ hasRestrictionOverriden = getattr(mod, 'restrictionOverridden', None)
+ # If module had broken fitting restrictions but now doesn't,
+ # ensure it is now valid, and remove restrictionOverridden
+ # variable. More in #1519
+ if not fit.ignoreRestrictions and hasRestrictionOverriden:
+ clean = False
+ if mod.fits(fit, False):
+ if not mod.hardpoint:
+ clean = True
+ elif fit.getHardpointsFree(mod.hardpoint) >= 0:
+ clean = True
+ if clean:
+ del mod.restrictionOverridden
+ hasRestrictionOverriden = not hasRestrictionOverriden
+
+ if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red
self.SetItemBackgroundColour(i, wx.Colour(204, 51, 51))
elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled
self.SetItemBackgroundColour(i, self.slotColour(mod.slot))
@@ -726,7 +741,7 @@ class FittingView(d.Display):
# noinspection PyPropertyAccess
def MakeSnapshot(self, maxColumns=1337):
if self.FVsnapshot:
- del self.FVsnapshot
+ self.FVsnapshot = None
tbmp = wx.Bitmap(16, 16)
tdc = wx.MemoryDC()
diff --git a/gui/chrome_tabs.py b/gui/chrome_tabs.py
index c2a1ed31c..521ac6168 100644
--- a/gui/chrome_tabs.py
+++ b/gui/chrome_tabs.py
@@ -20,6 +20,7 @@ from gui.bitmap_loader import BitmapLoader
from gui.utils import draw
from gui.utils import color as color_utils
from service.fit import Fit
+from gui.utils import fonts
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
@@ -357,7 +358,7 @@ class _TabRenderer:
self.tab_bitmap = None
self.tab_back_bitmap = None
self.padding = 4
- self.font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
+ self.font = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
self.tab_img = img
self.position = (0, 0) # Not used internally for rendering - helper for tab container
@@ -1322,7 +1323,7 @@ class PFNotebookPagePreview(wx.Frame):
self.padding = 15
self.transp = 0
- hfont = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
+ hfont = wx.Font(fonts.NORMAL, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
self.SetFont(hfont)
tx, ty = self.GetTextExtent(self.title)
@@ -1384,7 +1385,7 @@ class PFNotebookPagePreview(wx.Frame):
mdc.SetBackground(wx.Brush(color))
mdc.Clear()
- font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
+ font = wx.Font(11, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
mdc.SetFont(font)
x, y = mdc.GetTextExtent(self.title)
diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py
index 5675c835e..a70bea814 100644
--- a/gui/copySelectDialog.py
+++ b/gui/copySelectDialog.py
@@ -35,7 +35,7 @@ class CopySelectDialog(wx.Dialog):
style=wx.DEFAULT_DIALOG_STYLE)
mainSizer = wx.BoxSizer(wx.VERTICAL)
- copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "CREST", "MultiBuy"]
+ copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "ESI", "MultiBuy"]
copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format",
CopySelectDialog.copyFormatEftImps: "EFT text format",
CopySelectDialog.copyFormatXml: "EVE native XML format",
diff --git a/gui/esiFittings.py b/gui/esiFittings.py
index 99d8f33eb..af3da77a5 100644
--- a/gui/esiFittings.py
+++ b/gui/esiFittings.py
@@ -157,6 +157,19 @@ class EveFittings(wx.Frame):
self.statusbar.SetStatusText(msg)
+class ESIServerExceptionHandler(object):
+ def __init__(self, parentWindow, ex):
+ dlg = wx.MessageDialog(parentWindow,
+ "There was an issue starting up the localized server, try setting "
+ "Login Authentication Method to Manual by going to Preferences -> EVE SS0 -> "
+ "Login Authentication Method. If this doesn't fix the problem please file an "
+ "issue on Github.",
+ "Add Character Error",
+ wx.OK | wx.ICON_ERROR)
+ dlg.ShowModal()
+ pyfalog.error(ex)
+
+
class ESIExceptionHandler(object):
# todo: make this a generate excetpion handler for all calls
def __init__(self, parentWindow, ex):
@@ -325,10 +338,12 @@ class SsoCharacterMgmt(wx.Dialog):
self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.lcCharacters.SetColumnWidth(1, wx.LIST_AUTOSIZE)
- @staticmethod
- def addChar(event):
- sEsi = Esi.getInstance()
- sEsi.login()
+ def addChar(self, event):
+ try:
+ sEsi = Esi.getInstance()
+ sEsi.login()
+ except Exception as ex:
+ ESIServerExceptionHandler(self, ex)
def delChar(self, event):
item = self.lcCharacters.GetFirstSelected()
diff --git a/gui/utils/exportHtml.py b/gui/utils/exportHtml.py
index 0d5456dd0..796e3aa4c 100644
--- a/gui/utils/exportHtml.py
+++ b/gui/utils/exportHtml.py
@@ -90,7 +90,7 @@ class exportHtmlThread(threading.Thread):