diff --git a/eos/db/__init__.py b/eos/db/__init__.py
index 789141435..a02979112 100644
--- a/eos/db/__init__.py
+++ b/eos/db/__init__.py
@@ -66,7 +66,7 @@ from eos.db.saveddata.queries import getUser, getCharacter, getFit, getFitsWithS
getCharacterList, getPrice, getDamagePatternList, getDamagePattern, \
getFitList, getFleetList, getFleet, save, remove, commit, add, \
getCharactersForUser, getMiscData, getSquadsIDsWithFitID, getWing, \
- getSquad, getBoosterFits, getProjectedFits
+ getSquad, getBoosterFits, getProjectedFits, getTargetResistsList, getTargetResists
#If using in memory saveddata, you'll want to reflect it so the data structure is good.
if config.saveddata_connectionstring == "sqlite:///:memory:":
diff --git a/eos/db/migration.py b/eos/db/migration.py
index 2c67b8334..81399f4a5 100644
--- a/eos/db/migration.py
+++ b/eos/db/migration.py
@@ -4,6 +4,7 @@ def update(saveddata_engine):
checkPriceFailures(saveddata_engine)
checkApiDefaultChar(saveddata_engine)
checkFitBooster(saveddata_engine)
+ checktargetResists(saveddata_engine)
def checkPriceFailures(saveddata_engine):
# Check if we have 'failed' column
@@ -57,3 +58,19 @@ def checkFitBooster(saveddata_engine):
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN booster BOOLEAN;")
# Set NULL data to 0 (needed in case of downgrade, see GH issue #62
saveddata_engine.execute("UPDATE fits SET booster = 0 WHERE booster IS NULL;")
+
+def checktargetResists(saveddata_engine):
+ try:
+ saveddata_engine.execute("SELECT * FROM fits LIMIT 1")
+ # If table doesn't exist, it means we're doing everything from scratch
+ # and sqlalchemy will process everything as needed
+ except sqlalchemy.exc.DatabaseError:
+ pass
+ # If not, we're running on top of existing DB
+ else:
+ # Check that we have columns
+ try:
+ saveddata_engine.execute("SELECT targetResistsID FROM fits LIMIT 1")
+ # If we don't, create them
+ except sqlalchemy.exc.DatabaseError:
+ saveddata_engine.execute("ALTER TABLE fits ADD COLUMN targetResistsID INTEGER;")
diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index 70c7b22ec..31e71c01a 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -1,3 +1,3 @@
__all__ = ["character", "fit", "module", "user", "skill", "price",
"booster", "drone", "implant", "fleet", "damagePattern",
- "miscData"]
+ "miscData", "targetResists"]
diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py
index ccb8bbb71..b06b8f924 100644
--- a/eos/db/saveddata/fit.py
+++ b/eos/db/saveddata/fit.py
@@ -26,10 +26,11 @@ from eos.db.saveddata.module import modules_table
from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_table
-from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern
+from eos.types import Fit, Module, User, Booster, Drone, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.effectHandlerHelpers import HandledModuleList, HandledDroneList, \
HandledImplantBoosterList, HandledProjectedModList, HandledProjectedDroneList, \
HandledProjectedFitList, HandledCargoList
+
fits_table = Table("fits", saveddata_meta,
Column("ID", Integer, primary_key = True),
Column("ownerID", ForeignKey("users.ID"), nullable = True, index = True),
@@ -38,7 +39,8 @@ fits_table = Table("fits", saveddata_meta,
Column("timestamp", Integer, nullable = False),
Column("characterID", ForeignKey("characters.ID"), nullable = True),
Column("damagePatternID", ForeignKey("damagePatterns.ID"), nullable=True),
- Column("booster", Boolean, nullable = False, index = True, default = 0))
+ Column("booster", Boolean, nullable = False, index = True, default = 0),
+ Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True))
projectedFits_table = Table("projectedFits", saveddata_meta,
Column("sourceID", ForeignKey("fits.ID"), primary_key = True),
@@ -64,6 +66,7 @@ mapper(Fit, fits_table,
secondary = fitImplants_table),
"_Fit__character" : relation(Character, backref = "fits"),
"_Fit__damagePattern" : relation(DamagePattern),
+ "_Fit__targetResists" : relation(TargetResists),
"_Fit__projectedFits" : relation(Fit,
primaryjoin = projectedFits_table.c.victimID == fits_table.c.ID,
secondaryjoin = fits_table.c.ID == projectedFits_table.c.sourceID,
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index ddb8d53ac..e939bb65f 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -19,7 +19,7 @@
from eos.db.util import processEager, processWhere
from eos.db import saveddata_session, sd_lock
-from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad
+from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists
from eos.db.saveddata.fleet import squadmembers_table
from eos.db.saveddata.fit import projectedFits_table
from sqlalchemy.sql import and_
@@ -322,6 +322,12 @@ def getDamagePatternList(eager=None):
patterns = saveddata_session.query(DamagePattern).options(*eager).all()
return patterns
+def getTargetResistsList(eager=None):
+ eager = processEager(eager)
+ with sd_lock:
+ patterns = saveddata_session.query(TargetResists).options(*eager).all()
+ return patterns
+
@cachedQuery(DamagePattern, 1, "lookfor")
def getDamagePattern(lookfor, eager=None):
if isinstance(lookfor, int):
@@ -340,6 +346,24 @@ def getDamagePattern(lookfor, eager=None):
raise TypeError("Need integer or string as argument")
return pattern
+@cachedQuery(TargetResists, 1, "lookfor")
+def getTargetResists(lookfor, eager=None):
+ if isinstance(lookfor, int):
+ if eager is None:
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).get(lookfor)
+ else:
+ eager = processEager(eager)
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.ID == lookfor).first()
+ elif isinstance(lookfor, basestring):
+ eager = processEager(eager)
+ with sd_lock:
+ pattern = saveddata_session.query(TargetResists).options(*eager).filter(TargetResists.name == lookfor).first()
+ else:
+ raise TypeError("Need integer or string as argument")
+ return pattern
+
def searchFits(nameLike, where=None, eager=None):
if not isinstance(nameLike, basestring):
raise TypeError("Need string as argument")
@@ -361,7 +385,7 @@ def getSquadsIDsWithFitID(fitID):
return squads
else:
raise TypeError("Need integer as argument")
-
+
def getProjectedFits(fitID):
if isinstance(fitID, int):
with sd_lock:
@@ -369,7 +393,7 @@ def getProjectedFits(fitID):
fits = saveddata_session.query(Fit).filter(filter).all()
return fits
else:
- raise TypeError("Need integer as argument")
+ raise TypeError("Need integer as argument")
def add(stuff):
with sd_lock:
diff --git a/eos/db/saveddata/targetResists.py b/eos/db/saveddata/targetResists.py
new file mode 100644
index 000000000..f7106fe45
--- /dev/null
+++ b/eos/db/saveddata/targetResists.py
@@ -0,0 +1,35 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+from sqlalchemy import Table, Column, Integer, Float, ForeignKey, String
+from sqlalchemy.orm import mapper
+
+from eos.db import saveddata_meta
+from eos.types import TargetResists
+
+targetResists_table = Table("targetResists", saveddata_meta,
+ Column("ID", Integer, primary_key = True),
+ Column("name", String),
+ Column("emAmount", Float),
+ Column("thermalAmount", Float),
+ Column("kineticAmount", Float),
+ Column("explosiveAmount", Float),
+ Column("ownerID", ForeignKey("users.ID"), nullable=True))
+
+mapper(TargetResists, targetResists_table)
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index e2aaaa847..65df79ebf 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -99,6 +99,17 @@ class Fit(object):
self.extraAttributes.original = self.EXTRA_ATTRIBUTES
self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
+ @property
+ def targetResists(self):
+ return self.__targetResists
+
+ @targetResists.setter
+ def targetResists(self, targetResists):
+ self.__targetResists = targetResists
+ self.__weaponDPS = None
+ self.__weaponVolley = None
+ self.__droneDPS = None
+
@property
def damagePattern(self):
return self.__damagePattern
@@ -809,9 +820,9 @@ class Fit(object):
weaponDPS = 0
droneDPS = 0
weaponVolley = 0
-
+ print "calc weapons with: %s"%self.targetResists
for mod in self.modules:
- dps, volley = mod.damageStats
+ dps, volley = mod.damageStats(self.targetResists)
weaponDPS += dps
weaponVolley += volley
@@ -838,6 +849,7 @@ class Fit(object):
copy.ship = deepcopy(self.ship, memo)
copy.name = "%s copy" % self.name
copy.damagePattern = self.damagePattern
+ copy.targetResists = self.targetResists
toCopy = ("modules", "drones", "implants", "boosters", "projectedModules", "projectedDrones")
for name in toCopy:
diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py
index 2e68816c3..de65e6224 100644
--- a/eos/saveddata/module.py
+++ b/eos/saveddata/module.py
@@ -44,7 +44,7 @@ class Hardpoint(Enum):
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
"""An instance of this class represents a module together with its charge and modified attributes"""
- DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage")
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
MINING_ATTRIBUTES = ("miningAmount", )
def __init__(self, item):
@@ -308,8 +308,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
self.__itemModifiedAttributes.clear()
- @property
- def damageStats(self):
+ def damageStats(self, targetResists):
if self.__dps == None:
if self.isEmpty:
self.__dps = 0
@@ -317,9 +316,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
else:
if self.state >= State.ACTIVE:
if self.charge:
- volley = sum(map(lambda attr: self.getModifiedChargeAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
+ func = self.getModifiedChargeAttr
else:
- volley = sum(map(lambda attr: self.getModifiedItemAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
+ func = self.getModifiedItemAttr
+
+ volley = sum(map(lambda attr: (func("%sDamage"%attr) or 0) * (1-getattr(targetResists, "%sAmount"%attr, 0)), self.DAMAGE_TYPES))
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
if volley:
cycleTime = self.cycleTime
diff --git a/eos/saveddata/targetResists.py b/eos/saveddata/targetResists.py
new file mode 100644
index 000000000..7512e1dfc
--- /dev/null
+++ b/eos/saveddata/targetResists.py
@@ -0,0 +1,82 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of eos.
+#
+# eos is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# eos is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with eos. If not, see .
+#===============================================================================
+
+import re
+
+class TargetResists(object):
+ # also determined import/export order - VERY IMPORTANT
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+
+ def __init__(self, emAmount = 0, thermalAmount = 0, kineticAmount = 0, explosiveAmount = 0):
+ print "new"
+ self.emAmount = emAmount
+ self.thermalAmount = thermalAmount
+ self.kineticAmount = kineticAmount
+ self.explosiveAmount = explosiveAmount
+
+ @classmethod
+ def importPatterns(cls, text):
+ lines = re.split('[\n\r]+', text)
+ patterns = []
+
+ for line in lines:
+ if line.strip()[0] == "#": # comments
+ continue
+ line = line.split('#',1)[0] # allows for comments
+ type, data = line.rsplit('=',1)
+ type, data = type.strip(), data.split(',')
+
+ #if type != "TargetResists":
+ #continue
+
+ name, data = data[0], data[1:5]
+ #print name, data
+ fields = {}
+
+ for index, val in enumerate(data):
+ val = float(val)
+ try:
+ assert 0 <= val <= 100
+ fields["%sAmount" % cls.DAMAGE_TYPES[index]] = val/100
+ except:
+ continue
+
+ if len(fields) == 4: # Avoid possible blank lines
+ print name, fields
+ pattern = TargetResists(**fields)
+ pattern.name = name.strip()
+ patterns.append(pattern)
+
+ return patterns
+
+ EXPORT_FORMAT = "TargetResists = %s,%.1f,%.1f,%.1f,%.1f\n"
+ @classmethod
+ def exportPatterns(cls, *patterns):
+ out = "# Exported from pyfa\n#\n"
+ out += "# Values are in following format:\n"
+ out += "# TargetResists = [name],[EM %],[Thermal %],[Kinetic %],[Explosive %]\n\n"
+ for dp in patterns:
+ out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount*100, dp.thermalAmount*100, dp.kineticAmount*100, dp.explosiveAmount*100)
+
+ return out.strip()
+
+ def __deepcopy__(self, memo):
+ p = TargetResists(self.emAmount, self.thermalAmount, self.kineticAmount, self.explosiveAmount)
+ p.name = "%s copy" % self.name
+ return p
diff --git a/eos/types.py b/eos/types.py
index 9896e526a..3e5223f4b 100644
--- a/eos/types.py
+++ b/eos/types.py
@@ -22,6 +22,7 @@ MetaGroup, AttributeInfo, Unit, EffectInfo, MetaType, MetaData, Traits
from eos.saveddata.price import Price
from eos.saveddata.user import User
from eos.saveddata.damagePattern import DamagePattern
+from eos.saveddata.targetResists import TargetResists
from eos.saveddata.character import Character, Skill
from eos.saveddata.module import Module, State, Slot, Hardpoint, Rack
from eos.saveddata.drone import Drone
diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py
index 4f93b8424..bd385255d 100644
--- a/gui/builtinContextMenus/__init__.py
+++ b/gui/builtinContextMenus/__init__.py
@@ -1,2 +1,3 @@
__all__ = ["moduleAmmoPicker", "itemStats", "damagePattern", "marketJump", "droneSplit", "itemRemove",
- "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo", "shipJump"]
+ "droneRemoveStack", "ammoPattern", "project", "factorReload", "whProjector", "cargo", "shipJump",
+ "targetResists"]
diff --git a/gui/builtinContextMenus/targetResists.py b/gui/builtinContextMenus/targetResists.py
new file mode 100644
index 000000000..24904b429
--- /dev/null
+++ b/gui/builtinContextMenus/targetResists.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+
+from gui.contextMenu import ContextMenu
+import gui.mainFrame
+import service
+import gui.globalEvents as GE
+import wx
+from gui import bitmapLoader
+
+class TargetResists(ContextMenu):
+ def __init__(self):
+ self.mainFrame = gui.mainFrame.MainFrame.getInstance()
+
+ def display(self, srcContext, selection):
+ if self.mainFrame.getActiveFit() is None or srcContext not in ("firepowerViewFull",):
+ return False
+
+ sTR = service.TargetResists.getInstance()
+ self.patterns = sTR.getTargetResistsList()
+ self.patterns.sort( key=lambda p: (p.name in ["None"], p.name) )
+ return len(self.patterns) > 0
+
+ def getText(self, itmContext, selection):
+ return "Target Resists"
+
+ def activate(self, fullContext, selection, i):
+ pass
+
+ def handleResistSwitch(self, event):
+ pattern = self.patternIds.get(event.Id, False)
+ if pattern is False:
+ event.Skip()
+ return
+
+ sFit = service.Fit.getInstance()
+ fitID = self.mainFrame.getActiveFit()
+ sFit.setTargetResists(fitID, pattern)
+ wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
+
+ def addPattern(self, menu, pattern, currBase = None):
+ id = wx.NewId()
+ name = pattern.name if pattern is not None else "No Profile"
+ self.patternIds[id] = pattern
+ if currBase:
+ item = wx.MenuItem(menu, id, currBase)
+ else:
+ item = wx.MenuItem(menu, id, name)
+
+ item.pattern = pattern
+ return item
+
+ def addSeperator(self, m, text):
+ id = wx.NewId()
+ m.Append(id, u'─ %s ─' % text)
+ m.Enable(id, False)
+
+ def getSubMenu(self, context, selection, menu, i):
+ self.context = context
+ menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
+ m = wx.Menu()
+ m.Bind(wx.EVT_MENU, self.handleResistSwitch)
+ self.patternIds = {}
+
+ # @todo: this whole thing is a mess, please fix
+ # Maybe look into processing resists into a dict and iterating through
+ # dict to make submenus instead of this shitty logic
+ items = []
+ nameBase = None
+ sub = None
+
+ m.AppendItem(self.addPattern(m, None)) # Add reset
+ m.AppendSeparator()
+ for pattern in self.patterns:
+ start, end = pattern.name.find('['), pattern.name.find(']')
+ if start is not -1 and end is not -1:
+ currBase = pattern.name[start+1:end]
+ else:
+ currBase = None
+
+ if nameBase is None or nameBase != currBase:
+ sub = None
+ base = pattern
+ nameBase = currBase
+ item = self.addPattern(m, pattern, currBase)
+ items.append(item)
+ else:
+ if sub is None:
+ sub = wx.Menu()
+ sub.Bind(wx.EVT_MENU, self.handleResistSwitch)
+ item.SetSubMenu(sub)
+ sub.AppendItem(self.addPattern(sub, base))
+
+ sub.AppendItem(self.addPattern(sub, pattern))
+ for item in items:
+ m.AppendItem(item)
+ return m
+
+TargetResists.register()
diff --git a/gui/builtinStatsViews/firepowerViewFull.py b/gui/builtinStatsViews/firepowerViewFull.py
index 1b26b2662..7f18b6a0c 100644
--- a/gui/builtinStatsViews/firepowerViewFull.py
+++ b/gui/builtinStatsViews/firepowerViewFull.py
@@ -40,13 +40,20 @@ class FirepowerViewFull(StatsView):
def populatePanel(self, contentPanel, headerPanel):
contentSizer = contentPanel.GetSizer()
parent = self.panel = contentPanel
+
self.headerPanel = headerPanel
+ headerContentSizer = wx.BoxSizer(wx.HORIZONTAL)
+ hsizer = headerPanel.GetSizer()
+ hsizer.Add(headerContentSizer,0,0,0)
+ self.stEff = wx.StaticText(headerPanel, wx.ID_ANY, "( Effective )")
+ headerContentSizer.Add(self.stEff)
+ headerPanel.GetParent().AddToggleItem(self.stEff)
panel = "full"
sizerFirepower = wx.FlexGridSizer(1, 4)
sizerFirepower.AddGrowableCol(1)
-
+
contentSizer.Add( sizerFirepower, 0, wx.EXPAND, 0)
counter = 0
@@ -129,6 +136,10 @@ class FirepowerViewFull(StatsView):
def refreshPanel(self, fit):
#If we did anything intresting, we'd update our labels to reflect the new fit's stats here
+ if fit is not None and fit.targetResists is not None:
+ self.stEff.Show()
+ else:
+ self.stEff.Hide()
stats = (("labelFullDpsWeapon", lambda: fit.weaponDPS, 3, 0, 0, "%s DPS",None),
("labelFullDpsDrone", lambda: fit.droneDPS, 3, 0, 0, "%s DPS", None),
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 05ec3e014..e6c08cecd 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -46,6 +46,7 @@ from gui.shipBrowser import ShipBrowser, FitSelected
from gui.characterEditor import CharacterEditor
from gui.characterSelection import CharacterSelection
from gui.patternEditor import DmgPatternEditorDlg
+from gui.resistsEditor import ResistsEditorDlg
from gui.preferenceDialog import PreferenceDialog
from gui.graphFrame import GraphFrame
from gui.copySelectDialog import CopySelectDialog
@@ -311,6 +312,11 @@ class MainFrame(wx.Frame):
dlg=CharacterEditor(self)
dlg.Show()
+ def showTargetResistsEditor(self, event):
+ dlg=ResistsEditorDlg(self)
+ dlg.ShowModal()
+ dlg.Destroy()
+
def showDamagePatternEditor(self, event):
dlg=DmgPatternEditorDlg(self)
dlg.ShowModal()
@@ -392,6 +398,8 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.showCharacterEditor, id=menuBar.characterEditorId)
# Damage pattern editor
self.Bind(wx.EVT_MENU, self.showDamagePatternEditor, id=menuBar.damagePatternEditorId)
+ # Target Resists editor
+ self.Bind(wx.EVT_MENU, self.showTargetResistsEditor, id=menuBar.targetResistsEditorId)
# Import dialog
self.Bind(wx.EVT_MENU, self.showImportDialog, id=wx.ID_OPEN)
# Export dialog
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 73577578d..06e1dfabf 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -28,6 +28,7 @@ class MainMenuBar(wx.MenuBar):
def __init__(self):
self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId()
+ self.targetResistsEditorId = wx.NewId()
self.graphFrameId = wx.NewId()
self.backupFitsId = wx.NewId()
self.exportSkillsNeededId = wx.NewId()
@@ -82,6 +83,10 @@ class MainMenuBar(wx.MenuBar):
damagePatternEditItem.SetBitmap(bitmapLoader.getBitmap("damagePattern_small", "icons"))
windowMenu.AppendItem(damagePatternEditItem)
+ targetResistsEditItem = wx.MenuItem(windowMenu, self.targetResistsEditorId, "Target Resists Editor\tCTRL+R")
+ targetResistsEditItem.SetBitmap(bitmapLoader.getBitmap("explosive_big", "icons"))
+ windowMenu.AppendItem(targetResistsEditItem)
+
graphFrameItem = wx.MenuItem(windowMenu, self.graphFrameId, "Graphs\tCTRL+G")
graphFrameItem.SetBitmap(bitmapLoader.getBitmap("graphs_small", "icons"))
windowMenu.AppendItem(graphFrameItem)
diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py
new file mode 100644
index 000000000..ad53e952b
--- /dev/null
+++ b/gui/resistsEditor.py
@@ -0,0 +1,353 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+#===============================================================================
+
+import wx
+import bitmapLoader
+import service
+from gui.utils.clipboard import toClipboard, fromClipboard
+
+class ResistsEditorDlg (wx.Dialog):
+
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+
+ def __init__(self, parent):
+ wx.Dialog.__init__ (self, parent, id = wx.ID_ANY, title = u"Target Resists Editor", size = wx.Size( 350,240 ))
+
+ self.block = False
+ self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ sTR = service.TargetResists.getInstance()
+
+ self.choices = sTR.getTargetResistsList()
+
+ # Sort the remaining list and continue on
+ self.choices.sort(key=lambda p: p.name)
+ self.ccResists = wx.Choice(self, choices=map(lambda p: p.name, self.choices))
+ self.ccResists.Bind(wx.EVT_CHOICE, self.patternChanged)
+ self.ccResists.SetSelection(0)
+
+ self.namePicker = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
+ self.namePicker.Bind(wx.EVT_TEXT_ENTER, self.processRename)
+ self.namePicker.Hide()
+
+ self.btnSave = wx.Button(self, wx.ID_SAVE)
+ self.btnSave.Hide()
+ self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
+
+ size = None
+ headerSizer.Add(self.ccResists, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT|wx.LEFT, 3)
+
+ buttons = (("new", wx.ART_NEW),
+ ("rename", bitmapLoader.getBitmap("rename", "icons")),
+ ("copy", wx.ART_COPY),
+ ("delete", wx.ART_DELETE))
+ for name, art in buttons:
+ bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
+ btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
+ if size is None:
+ size = btn.GetSize()
+
+ btn.SetMinSize(size)
+ btn.SetMaxSize(size)
+
+ btn.Layout()
+ setattr(self, name, btn)
+ btn.Enable(True)
+ btn.SetToolTipString("%s resist profile" % name.capitalize())
+ headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+ mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
+
+ self.sl = wx.StaticLine(self)
+ mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+
+ resistEditSizer = wx.FlexGridSizer(2, 6, 0, 2)
+ resistEditSizer.AddGrowableCol(0)
+ resistEditSizer.AddGrowableCol(5)
+ resistEditSizer.SetFlexibleDirection(wx.BOTH)
+ resistEditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
+
+ width = -1
+ defSize = wx.Size(50,-1)
+
+ for i, type in enumerate(self.DAMAGE_TYPES):
+ if i%2:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx. LEFT
+ border = 25
+ else:
+ style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
+ border = 5
+
+ bmp = wx.StaticBitmap(self, wx.ID_ANY, bitmapLoader.getBitmap("%s_big"%type, "icons"))
+ resistEditSizer.Add(bmp, 0, style, border)
+ # set text edit
+ setattr(self, "%sEdit"%type, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize))
+ editObj = getattr(self, "%sEdit"%type)
+ resistEditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ resistEditSizer.Add(wx.StaticText( self, wx.ID_ANY, u"%", wx.DefaultPosition, wx.DefaultSize, 0 ), 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
+ editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated)
+
+ contentSizer.Add(resistEditSizer, 1, wx.EXPAND | wx.ALL, 5)
+ self.slfooter = wx.StaticLine(self)
+ contentSizer.Add(self.slfooter, 0, wx.EXPAND | wx.TOP, 5)
+
+ footerSizer = wx.BoxSizer(wx.HORIZONTAL)
+ perSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.stNotice = wx.StaticText(self, wx.ID_ANY, u"")
+ self.stNotice.Wrap(-1)
+ perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
+
+ footerSizer.Add(perSizer, 1, wx.ALIGN_CENTER_VERTICAL, 5)
+
+ self.totSizer = wx.BoxSizer(wx.VERTICAL)
+
+ contentSizer.Add(footerSizer, 0, wx.EXPAND, 5)
+
+ mainSizer.Add(contentSizer, 1, wx.EXPAND, 0)
+
+ if "wxGTK" in wx.PlatformInfo:
+ self.closeBtn = wx.Button( self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0 )
+ mainSizer.Add( self.closeBtn, 0, wx.ALL|wx.ALIGN_RIGHT, 5 )
+ self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent)
+
+ self.SetSizer(mainSizer)
+
+ importExport = (("Import", wx.ART_FILE_OPEN, "from"),
+ ("Export", wx.ART_FILE_SAVE_AS, "to"))
+
+ for name, art, direction in importExport:
+ bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
+ btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
+
+ btn.SetMinSize( btn.GetSize() )
+ btn.SetMaxSize( btn.GetSize() )
+
+ btn.Layout()
+ setattr(self, name, btn)
+ btn.Enable(True)
+ btn.SetToolTipString("%s patterns %s clipboard" % (name, direction) )
+ footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
+
+ self.Layout()
+ bsize = self.GetBestSize()
+ self.SetSize((-1,bsize.height))
+
+ self.new.Bind(wx.EVT_BUTTON, self.newPattern)
+ self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
+ self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
+ self.delete.Bind(wx.EVT_BUTTON, self.deletePattern)
+ self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
+ self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
+
+ self.patternChanged()
+
+ def closeEvent(self, event):
+ self.Destroy()
+
+ def ValuesUpdated(self, event=None):
+ if self.block:
+ return
+ if event is not None:
+ print event.GetString()
+
+ try:
+ p = self.getActivePattern()
+
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+
+ if editObj.GetValue() == "":
+ # if we are blank, overwrite with 0
+ editObj.ChangeValue("0")
+ editObj.SetInsertionPointEnd()
+
+ value = float(editObj.GetValue())
+
+ # assertion, because they're easy
+ assert 0 <= value <= 100
+
+ # if everything checks out, set resist attribute
+ setattr(p, "%sAmount"%type, value/100)
+
+ self.stNotice.SetLabel("")
+ self.totSizer.Layout()
+
+ if event is not None:
+ # If we get here, everything is normal. Reset color
+ event.EventObject.SetForegroundColour(wx.NullColor)
+ event.Skip()
+
+ service.TargetResists.getInstance().saveChanges(p)
+
+ except ValueError:
+ event.EventObject.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Formatting (decimals only)")
+ except AssertionError:
+ event.EventObject.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Range (must be 0-100)")
+ finally:
+ self.Refresh() # Refresh for color changes to take effect immediately
+
+ def restrict(self):
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable(False)
+ self.rename.Enable(False)
+ self.delete.Enable(False)
+
+ def unrestrict(self):
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.Enable()
+ self.rename.Enable()
+ self.delete.Enable()
+
+ def getActivePattern(self):
+ if len(self.choices) == 0:
+ return None
+
+ return self.choices[self.ccResists.GetSelection()]
+
+ def patternChanged(self, event=None):
+ p = self.getActivePattern()
+ if p is None:
+ return
+
+ self.block = True
+ for field in self.DAMAGE_TYPES:
+ edit = getattr(self, "%sEdit" % field)
+ amount = getattr(p, "%sAmount" % field)*100
+ edit.ChangeValue(str(amount))
+
+ self.block = False
+ self.ValuesUpdated()
+
+ def newPattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ p = sTR.newPattern()
+ self.choices.append(p)
+ id = self.ccResists.Append(p.name)
+ self.ccResists.SetSelection(id)
+ self.btnSave.SetLabel("Create")
+
+ # reset values
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+ editObj.ChangeValue("0")
+ editObj.SetForegroundColour(wx.NullColor)
+
+ self.Refresh()
+ self.renamePattern()
+
+ def renamePattern(self,event=None):
+ if event is not None:
+ self.btnSave.SetLabel("Rename")
+
+ self.ccResists.Hide()
+ self.namePicker.Show()
+ self.headerSizer.Replace(self.ccResists, self.namePicker)
+ self.namePicker.SetFocus()
+ self.namePicker.SetValue(self.getActivePattern().name)
+
+ for btn in (self.new, self.rename, self.delete, self.copy):
+ btn.Hide()
+ self.headerSizer.Remove(btn)
+
+ self.headerSizer.Add(self.btnSave, 0, wx.ALIGN_CENTER)
+ self.btnSave.Show()
+ self.headerSizer.Layout()
+ if event is not None:
+ event.Skip()
+
+ def processRename(self, event):
+ newName = self.namePicker.GetLineText(0)
+ self.stNotice.SetLabel("")
+
+ p = self.getActivePattern()
+ for pattern in self.choices:
+ if pattern.name == newName and p != pattern:
+ self.stNotice.SetLabel("Name already used, please pick another")
+ return
+
+ if newName == "":
+ self.stNotice.SetLabel("Invalid name")
+ return
+
+ sTR = service.TargetResists.getInstance()
+ sTR.renamePattern(p, newName)
+
+ self.headerSizer.Replace(self.namePicker, self.ccResists)
+ self.ccResists.Show()
+ self.namePicker.Hide()
+ self.btnSave.Hide()
+ self.headerSizer.Remove(self.btnSave)
+ for btn in (self.new, self.rename, self.delete, self.copy):
+ self.headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+ btn.Show()
+
+ sel = self.ccResists.GetSelection()
+ self.ccResists.Delete(sel)
+ self.ccResists.Insert(newName, sel)
+ self.ccResists.SetSelection(sel)
+ self.ValuesUpdated()
+ self.unrestrict()
+
+ def copyPattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ p = sTR.copyPattern(self.getActivePattern())
+ self.choices.append(p)
+ id = self.ccResists.Append(p.name)
+ self.ccResists.SetSelection(id)
+ self.btnSave.SetLabel("Copy")
+ self.renamePattern()
+ self.patternChanged()
+
+ def deletePattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ sel = self.ccResists.GetSelection()
+ sTR.deletePattern(self.getActivePattern())
+ self.ccResists.Delete(sel)
+ self.ccResists.SetSelection(max(0, sel - 1))
+ del self.choices[sel]
+ self.patternChanged()
+
+ def __del__( self ):
+ pass
+
+ def importPatterns(self, event):
+ text = fromClipboard()
+ if text:
+ sTR = service.TargetResists.getInstance()
+ # @todo: fix return value and use that to determine label
+ sTR.importPatterns(text)
+ self.stNotice.SetLabel("Patterns imported from clipboard")
+ else:
+ self.stNotice.SetLabel("Could not import from clipboard")
+
+ def exportPatterns(self, event):
+ sTR = service.TargetResists.getInstance()
+ toClipboard( sTR.exportPatterns() )
+ self.stNotice.SetLabel("Patterns exported to clipboard")
diff --git a/service/__init__.py b/service/__init__.py
index 8ded8a66a..8e9e9f6f5 100644
--- a/service/__init__.py
+++ b/service/__init__.py
@@ -3,6 +3,7 @@ from service.fit import Fit
from service.attribute import Attribute
from service.character import Character
from service.damagePattern import DamagePattern
+from service.targetResists import TargetResists
from service.settings import SettingsProvider
from service.fleet import Fleet
from service.update import Update
diff --git a/service/fit.py b/service/fit.py
index f6f747cf2..9719fe8e1 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -83,6 +83,7 @@ class Fit(object):
def __init__(self):
self.pattern = DamagePattern.getInstance().getDamagePattern("Uniform")
+ self.targetResists = None
self.character = Character.getInstance().all5()
self.booster = False
self.dirtyFitIDs = set()
@@ -148,6 +149,7 @@ class Fit(object):
fit.ship = eos.types.Ship(eos.db.getItem(shipID))
fit.name = name if name is not None else "New %s" % fit.ship.item.name
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
fit.character = self.character
fit.booster = self.booster
eos.db.save(fit)
@@ -683,6 +685,23 @@ class Fit(object):
self.recalc(fit)
+ def getTargetResists(self, fitID):
+ if fitID is None:
+ return
+
+ fit = eos.db.getFit(fitID)
+ return fit.targetResists
+
+ def setTargetResists(self, fitID, pattern):
+ if fitID is None:
+ return
+ print "Set target resists: %s"%pattern
+ fit = eos.db.getFit(fitID)
+ fit.targetResists = pattern
+ eos.db.commit()
+
+ self.recalc(fit)
+
def getDamagePattern(self, fitID):
if fitID is None:
return
@@ -760,6 +779,7 @@ class Fit(object):
for fit in fits:
fit.character = self.character
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
return fits
def importFitFromBuffer(self, bufferStr, activeFit=None):
@@ -767,6 +787,7 @@ class Fit(object):
for fit in fits:
fit.character = self.character
fit.damagePattern = self.pattern
+ fit.targetResists = self.targetResists
return fits
def saveImportedFits(self, fits):
diff --git a/service/targetResists.py b/service/targetResists.py
new file mode 100644
index 000000000..bdca643ce
--- /dev/null
+++ b/service/targetResists.py
@@ -0,0 +1,92 @@
+#===============================================================================
+# Copyright (C) 2014 Ryan Holmes
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+#===============================================================================
+
+import eos.db
+import eos.types
+import copy
+
+class TargetResists():
+ instance = None
+ @classmethod
+ def getInstance(cls):
+ if cls.instance is None:
+ cls.instance = TargetResists()
+
+ return cls.instance
+
+ def __init__(self):
+ uniform = eos.db.getTargetResists("None")
+ if uniform is None:
+ uniform = eos.types.TargetResists(0,0,0,0)
+ uniform.name = "None"
+ eos.db.save(uniform)
+
+ def getTargetResistsList(self):
+ return eos.db.getTargetResistsList()
+
+ def getTargetResists(self, name):
+ print "Getting Target Resists: %s"%name
+ return eos.db.getTargetResists(name)
+
+ def newPattern(self):
+ p = eos.types.TargetResists(0, 0, 0, 0)
+ p.name = ""
+ return p
+
+ def renamePattern(self, p, newName):
+ p.name = newName
+ eos.db.save(p)
+
+ def deletePattern(self, p):
+ eos.db.remove(p)
+
+ def copyPattern(self, p):
+ newP = copy.deepcopy(p)
+ eos.db.save(newP)
+ return newP
+
+ def saveChanges(self, p):
+ eos.db.save(p)
+
+ def importPatterns(self, text):
+ lookup = {}
+ current = self.getTargetResistsList()
+ for pattern in current:
+ lookup[pattern.name] = pattern
+ try:
+ imports = eos.types.TargetResists.importPatterns(text)
+ for pattern in imports:
+ if pattern.name in lookup:
+ match = lookup[pattern.name]
+ match.__dict__.update(pattern.__dict__)
+ else:
+ eos.db.save(pattern)
+ eos.db.commit()
+ except:
+ pass
+
+ def exportPatterns(self):
+ patterns = self.getTargetResistsList()
+ for i in xrange(len(patterns) - 1, -1, -1):
+ if patterns[i].name in ("None"):
+ del patterns[i]
+
+ patterns.sort(key=lambda p: p.name)
+ return eos.types.TargetResists.exportPatterns(*patterns)
+