diff --git a/eos/db/migrations/upgrade24.py b/eos/db/migrations/upgrade24.py new file mode 100644 index 000000000..93e52b61c --- /dev/null +++ b/eos/db/migrations/upgrade24.py @@ -0,0 +1,14 @@ +""" +Migration 24 + +- Adds a boolean value to fit to signify if fit should ignore restrictions +""" +import sqlalchemy + + +def upgrade(saveddata_engine): + try: + saveddata_engine.execute("SELECT ignoreRestrictions FROM fits LIMIT 1") + except sqlalchemy.exc.DatabaseError: + saveddata_engine.execute("ALTER TABLE fits ADD COLUMN ignoreRestrictions BOOLEAN") + saveddata_engine.execute("UPDATE fits SET ignoreRestrictions = 0") diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py index 959c99287..30f323ee9 100644 --- a/eos/db/saveddata/fit.py +++ b/eos/db/saveddata/fit.py @@ -58,6 +58,7 @@ fits_table = Table("fits", saveddata_meta, Column("modeID", Integer, nullable=True), Column("implantLocation", Integer, nullable=False, default=ImplantLocation.FIT), Column("notes", String, nullable=True), + Column("ignoreRestrictions", Boolean, default=0), Column("created", DateTime, nullable=True, default=func.now()), Column("modified", DateTime, nullable=True, onupdate=func.now()) ) diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index 30119250e..f63352aa7 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -1404,7 +1404,7 @@ class Fit(object): @property def fits(self): for mod in self.modules: - if not mod.fits(self): + if not mod.isEmpty and not mod.fits(self): return False return True diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index a94a684e0..26492a8e4 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -389,10 +389,24 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__reloadForce = type def fits(self, fit, hardpointLimit=True): + """ + Function that determines if a module can be fit to the ship. We always apply slot restrictions no matter what + (too many assumptions made on this), however all other fitting restrictions are optional + """ + slot = self.slot if fit.getSlotsFree(slot) <= (0 if self.owner != fit else -1): return False + fits = self.__fitRestrictions(fit, hardpointLimit) + + if not fits and fit.ignoreRestrictions: + self.restrictionOverridden = True + fits = True + + return fits + + def __fitRestrictions(self, fit, hardpointLimit=True): # Check ship type restrictions fitsOnType = set() fitsOnGroup = set() @@ -413,8 +427,9 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if shipGroup is not None: fitsOnGroup.add(shipGroup) - if (len(fitsOnGroup) > 0 or len( - fitsOnType) > 0) and fit.ship.item.group.ID not in fitsOnGroup and fit.ship.item.ID not in fitsOnType: + if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \ + and fit.ship.item.group.ID not in fitsOnGroup \ + and fit.ship.item.ID not in fitsOnType: return False # AFAIK Citadel modules will always be restricted based on canFitShipType/Group. If we are fitting to a Citadel diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index ff8bb4bf6..1aebfe8ec 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -611,12 +611,13 @@ class FittingView(d.Display): slotMap[slot] = fit.getSlotsFree(slot) < 0 font = (self.GetClassDefaultAttributes()).font + for i, mod in enumerate(self.mods): self.SetItemBackgroundColour(i, self.GetBackgroundColour()) # only consider changing color if we're dealing with a Module if type(mod) is Module: - if slotMap[mod.slot]: # Color too many modules as red + if slotMap[mod.slot] or getattr(mod, 'restrictionOverridden', None): # 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)) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index 5e1ad08c6..6b67ebcdd 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -524,6 +524,9 @@ class MainFrame(wx.Frame): # Clipboard exports self.Bind(wx.EVT_MENU, self.exportToClipboard, id=wx.ID_COPY) + # Fitting Restrictions + self.Bind(wx.EVT_MENU, self.toggleIgnoreRestriction, id=menuBar.toggleIgnoreRestrictionID) + # Graphs self.Bind(wx.EVT_MENU, self.openGraphFrame, id=menuBar.graphFrameId) @@ -584,6 +587,24 @@ class MainFrame(wx.Frame): atable = wx.AcceleratorTable(actb) self.SetAcceleratorTable(atable) + def toggleIgnoreRestriction(self, event): + + sFit = Fit.getInstance() + fitID = self.getActiveFit() + fit = sFit.getFit(fitID) + + if not fit.ignoreRestrictions: + dlg = wx.MessageDialog(self, "Are you sure you wish to ignore fitting restrictions for the " + "current fit? This could lead to wildly inaccurate results and possible errors.", "Confirm", wx.YES_NO | wx.ICON_QUESTION) + else: + dlg = wx.MessageDialog(self, "Re-enabling fitting restrictions for this fit will also remove any illegal items " + "from the fit. Do you want to continue?", "Confirm", wx.YES_NO | wx.ICON_QUESTION) + result = dlg.ShowModal() == wx.ID_YES + dlg.Destroy() + if result: + sFit.toggleRestrictionIgnore(fitID) + wx.PostEvent(self, GE.FitChanged(fitID=fitID)) + def eveFittings(self, event): dlg = CrestFittings(self) dlg.Show() diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index 00402a7a3..bade53d3d 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -22,6 +22,7 @@ import wx import config from service.character import Character +from service.fit import Fit import gui.graphFrame import gui.globalEvents as GE from gui.bitmapLoader import BitmapLoader @@ -57,6 +58,7 @@ class MainMenuBar(wx.MenuBar): self.attrEditorId = wx.NewId() self.toggleOverridesId = wx.NewId() self.importDatabaseDefaultsId = wx.NewId() + self.toggleIgnoreRestrictionID = wx.NewId() if 'wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0): wx.ID_COPY = wx.NewId() @@ -96,6 +98,8 @@ class MainMenuBar(wx.MenuBar): editMenu.Append(self.saveCharId, "Save Character") editMenu.Append(self.saveCharAsId, "Save Character As...") editMenu.Append(self.revertCharId, "Revert Character") + editMenu.AppendSeparator() + self.ignoreRestrictionItem = editMenu.Append(self.toggleIgnoreRestrictionID, "Ignore Fitting Restrictions") # Character menu windowMenu = wx.Menu() @@ -170,7 +174,6 @@ class MainMenuBar(wx.MenuBar): self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) def fitChanged(self, event): - pyfalog.debug("fitChanged triggered") enable = event.fitID is not None self.Enable(wx.ID_SAVEAS, enable) self.Enable(wx.ID_COPY, enable) @@ -185,4 +188,15 @@ class MainMenuBar(wx.MenuBar): self.Enable(self.saveCharAsId, char.isDirty) self.Enable(self.revertCharId, char.isDirty) + self.Enable(self.toggleIgnoreRestrictionID, enable) + + if event.fitID: + sFit = Fit.getInstance() + fit = sFit.getFit(event.fitID) + + if fit.ignoreRestrictions: + self.ignoreRestrictionItem.SetItemLabel("Enable Fitting Restrictions") + else: + self.ignoreRestrictionItem.SetItemLabel("Disable Fitting Restrictions") + event.Skip() diff --git a/service/fit.py b/service/fit.py index b14250f17..df9fbf6ff 100644 --- a/service/fit.py +++ b/service/fit.py @@ -240,6 +240,12 @@ class Fit(object): self.recalc(fit) fit.fill() + # this will loop through modules and set their restriction flag (set in m.fit()) + if fit.ignoreRestrictions: + for mod in fit.modules: + if not mod.isEmpty: + mod.fits(fit) + # Check that the states of all modules are valid self.checkStates(fit, None) @@ -883,6 +889,21 @@ class Fit(object): self.recalc(fit) return True + def toggleRestrictionIgnore(self, fitID): + pyfalog.debug("Toggling restriction ignore for fit ID: {0}", fitID) + fit = eos.db.getFit(fitID) + fit.ignoreRestrictions = not fit.ignoreRestrictions + + # remove invalid modules when switching back to enabled fitting restrictions + if not fit.ignoreRestrictions: + for m in fit.modules: + if not m.isEmpty and not m.fits(fit): + self.removeModule(fit.ID, m.modPosition) + + eos.db.commit() + self.recalc(fit) + return True + def toggleBooster(self, fitID, i): pyfalog.debug("Toggling booster for fit ID: {0}", fitID) fit = eos.db.getFit(fitID)