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