Merge pull request #168 from blitzmann/161-effDps

Implement effective DPS
This commit is contained in:
Ryan Holmes
2014-09-19 19:18:20 -04:00
28 changed files with 1075 additions and 151 deletions

View File

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

View File

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

View File

@@ -1,3 +1,3 @@
__all__ = ["character", "fit", "module", "user", "skill", "price",
"booster", "drone", "implant", "fleet", "damagePattern",
"miscData"]
"miscData", "targetResists"]

View File

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

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
#===============================================================================
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)

View File

@@ -69,26 +69,44 @@ class DamagePattern(object):
def importPatterns(cls, text):
lines = re.split('[\n\r]+', text)
patterns = []
numPatterns = 0
for line in lines:
line = line.split('#',1)[0] # allows for comments
name, data = line.rsplit('=',1)
name, data = name.strip(), ''.join(data.split()) # whitespace
try:
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(',')
except:
# Data isn't in correct format, continue to next line
continue
if type != "DamageProfile":
continue
numPatterns += 1
name, data = data[0], data[1:5]
fields = {}
for entry in data.split(','):
key, val = entry.split(':')
fields["%sAmount" % cls.importMap[key.lower()]] = float(val)
if len(fields) > 0: # Avoid possible blank lines
for index, val in enumerate(data):
try:
fields["%sAmount" % cls.DAMAGE_TYPES[index]] = int(val)
except:
continue
if len(fields) == 4: # Avoid possible blank lines
pattern = DamagePattern(**fields)
pattern.name = name
pattern.name = name.strip()
patterns.append(pattern)
return patterns
EXPORT_FORMAT = "%s = EM:%d, Therm:%d, Kin:%d, Exp:%d\n"
return patterns, numPatterns
EXPORT_FORMAT = "DamageProfile = %s,%d,%d,%d,%d\n"
@classmethod
def exportPatterns(cls, *patterns):
out = ""
out = "# Exported from pyfa\n#\n"
out += "# Values are in following format:\n"
out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
for dp in patterns:
out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)

View File

@@ -22,7 +22,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
from sqlalchemy.orm import validates, reconstructor
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage")
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
MINING_ATTRIBUTES = ("miningAmount",)
def __init__(self, item):
@@ -111,6 +111,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def dps(self):
return self.damageStats()
def damageStats(self, targetResists = None):
if self.__dps == None:
if self.dealsDamage is True and self.amountActive > 0:
if self.hasAmmo:
@@ -121,7 +124,9 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
getter = self.getModifiedItemAttr
cycleTime = self.getModifiedItemAttr(attr)
volley = sum(map(lambda d: getter(d), self.DAMAGE_ATTRIBUTES)) * self.amountActive
volley = sum(map(lambda d: (getter("%sDamage"%d) or 0) * (1-getattr(targetResists, "%sAmount"%d, 0)), self.DAMAGE_TYPES))
volley *= self.amountActive
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
self.__dps = volley / (cycleTime / 1000.0)
else:

View File

@@ -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
@@ -811,12 +822,12 @@ class Fit(object):
weaponVolley = 0
for mod in self.modules:
dps, volley = mod.damageStats
dps, volley = mod.damageStats(self.targetResists)
weaponDPS += dps
weaponVolley += volley
for drone in self.drones:
droneDPS += drone.dps
droneDPS += drone.damageStats(self.targetResists)
self.__weaponDPS = weaponDPS
self.__weaponVolley = weaponVolley
@@ -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", "cargo", "implants", "boosters", "projectedModules", "projectedDrones")
for name in toCopy:

View File

@@ -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,29 +308,23 @@ 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
self.__volley = 0
else:
if self.state >= State.ACTIVE:
if self.charge:
volley = sum(map(lambda attr: self.getModifiedChargeAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
else:
volley = sum(map(lambda attr: self.getModifiedItemAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
if volley:
cycleTime = self.cycleTime
self.__volley = volley
self.__dps = volley / (cycleTime / 1000.0)
else:
self.__volley = 0
self.__dps = 0
self.__dps = 0
self.__volley = 0
if not self.isEmpty and self.state >= State.ACTIVE:
if self.charge:
func = self.getModifiedChargeAttr
else:
self.__volley = 0
self.__dps = 0
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
self.__volley = volley
self.__dps = volley / (cycleTime / 1000.0)
return self.__dps, self.__volley
@@ -354,11 +348,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
@property
def dps(self):
return self.damageStats[0]
return self.damageStats(None)[0]
@property
def volley(self):
return self.damageStats[1]
return self.damageStats(None)[1]
@property
def reloadTime(self):

View File

@@ -0,0 +1,84 @@
#===============================================================================
# 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 <http://www.gnu.org/licenses/>.
#===============================================================================
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):
self.emAmount = emAmount
self.thermalAmount = thermalAmount
self.kineticAmount = kineticAmount
self.explosiveAmount = explosiveAmount
@classmethod
def importPatterns(cls, text):
lines = re.split('[\n\r]+', text)
patterns = []
numPatterns = 0
for line in lines:
try:
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(',')
except:
# Data isn't in correct format, continue to next line
continue
if type != "TargetResists":
continue
numPatterns += 1
name, data = data[0], data[1:5]
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
pattern = TargetResists(**fields)
pattern.name = name.strip()
patterns.append(pattern)
return patterns, numPatterns
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

View File

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

View File

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

View File

@@ -67,7 +67,7 @@ class ChangeAffectingSkills(ContextMenu):
rootMenu.Bind(wx.EVT_MENU, self.handleSkillChange, menuItem)
return menuItem
def getSubMenu(self, context, selection, menu, i):
def getSubMenu(self, context, selection, menu, i, pitem):
self.context = context
self.skillIds = {}

View File

@@ -13,35 +13,92 @@ class DamagePattern(ContextMenu):
return srcContext in ("resistancesViewFull",) and self.mainFrame.getActiveFit() is not None
def getText(self, itmContext, selection):
sDP = service.DamagePattern.getInstance()
self.patterns = sDP.getDamagePatternList()
self.patterns.sort( key=lambda p: (p.name in ["Selected Ammo",
"Uniform"], p.name) )
m = map(lambda p: p.name, self.patterns)
return m
def activate(self, fullContext, selection, i):
sDP = service.DamagePattern.getInstance()
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
sFit.setDamagePattern(fitID, self.patterns[i])
setattr(self.mainFrame,"_activeDmgPattern",self.patterns[i])
self.fit = sFit.getFit(fitID)
self.patterns = sDP.getDamagePatternList()
self.patterns.sort( key=lambda p: (p.name not in ["Uniform",
"Selected Ammo"], p.name) )
self.patternIds = {}
self.subMenus = {}
self.singles = []
# iterate and separate damage patterns based on "[Parent] Child"
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]
# set helper attr
setattr(pattern, "_name", pattern.name[end+1:].strip())
if currBase not in self.subMenus:
self.subMenus[currBase] = []
self.subMenus[currBase].append(pattern)
else:
self.singles.append(pattern)
# return list of names, with singles first followed by submenu names
self.m = map(lambda p: p.name, self.singles) + self.subMenus.keys()
return self.m
def handlePatternSwitch(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.setDamagePattern(fitID, pattern)
setattr(self.mainFrame,"_activeDmgPattern", pattern)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
def getBitmap(self, context, selection):
def addPattern(self, menu, pattern):
id = wx.NewId()
name = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
self.patternIds[id] = pattern
item = wx.MenuItem(menu, id, name)
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, item)
# set pattern attr to menu item
item.pattern = pattern
# determine active pattern
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
f = sFit.getFit(fitID)
dp = f.damagePattern
if dp is None:
if dp == pattern:
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
item.SetBitmap(bitmap)
return item
def getSubMenu(self, context, selection, menu, i, pitem):
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch) # this bit is required for some reason
if self.m[i] not in self.subMenus:
# if we're trying to get submenu to something that shouldn't have one,
# redirect event of the item to handlePatternSwitch and put pattern in
# our patternIds mapping, then return None for no submenu
id = pitem.GetId()
self.patternIds[id] = self.singles[i]
menu.Bind(wx.EVT_MENU, self.handlePatternSwitch, pitem)
if self.patternIds[id] == self.fit.damagePattern:
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
pitem.SetBitmap(bitmap)
return None
index = self.patterns.index(dp)
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
l = [None] * len(self.patterns)
l[index] = bitmap
sub = wx.Menu()
sub.Bind(wx.EVT_MENU, self.handlePatternSwitch)
return l
# Items that have a parent
for pattern in self.subMenus[self.m[i]]:
sub.AppendItem(self.addPattern(sub, pattern))
return sub
DamagePattern.register()

View File

@@ -116,7 +116,7 @@ class ModuleAmmoPicker(ContextMenu):
m.Append(id, u'%s' % text)
m.Enable(id, False)
def getSubMenu(self, context, selection, menu, i):
def getSubMenu(self, context, selection, menu, i, pitem):
self.context = context
menu.Bind(wx.EVT_MENU, self.handleAmmoSwitch)
m = wx.Menu()

View File

@@ -0,0 +1,114 @@
# -*- 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 = getattr(pattern, "_name", pattern.name) if pattern is not None else "No Profile"
self.patternIds[id] = pattern
item = wx.MenuItem(menu, id, name)
# set pattern attr to menu item
item.pattern = pattern
# determine active pattern
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
f = sFit.getFit(fitID)
tr = f.targetResists
if tr == pattern:
bitmap = bitmapLoader.getBitmap("state_active_small", "icons")
item.SetBitmap(bitmap)
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, pitem):
self.context = context
menu.Bind(wx.EVT_MENU, self.handleResistSwitch)
m = wx.Menu()
m.Bind(wx.EVT_MENU, self.handleResistSwitch)
self.patternIds = {}
self.subMenus = {}
self.singles = []
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]
# set helper attr
setattr(pattern, "_name", pattern.name[end+1:].strip())
if currBase not in self.subMenus:
self.subMenus[currBase] = []
self.subMenus[currBase].append(pattern)
else:
self.singles.append(pattern)
m.AppendItem(self.addPattern(m, None)) # Add reset
m.AppendSeparator()
# Single items, no parent
for pattern in self.singles:
m.AppendItem(self.addPattern(m, pattern))
# Items that have a parent
for menuName, patterns in self.subMenus.items():
# Create parent item for root menu that is simply name of parent
item = wx.MenuItem(menu, wx.NewId(), menuName)
# Create menu for child items
sub = wx.Menu()
sub.Bind(wx.EVT_MENU, self.handleResistSwitch)
# Apply child menu to parent item
item.SetSubMenu(sub)
# Append child items to child menu
for pattern in patterns:
sub.AppendItem(self.addPattern(sub, pattern))
m.AppendItem(item) #finally, append parent item to root menu
return m
TargetResists.register()

View File

@@ -17,7 +17,7 @@ class WhProjector(ContextMenu):
def activate(self, fullContext, selection, i):
pass
def getSubMenu(self, context, selection, menu, i):
def getSubMenu(self, context, selection, menu, i, pitem):
self.idmap = {}
menu.Bind(wx.EVT_MENU, self.handleSelection)
m = wx.Menu()

View File

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

View File

@@ -54,7 +54,7 @@ class ContextMenu(object):
item = wx.MenuItem(menu, id, text)
menu.info[id] = (m, fullContext, it)
sub = m.getSubMenu(srcContext, selection, menu, it)
sub = m.getSubMenu(srcContext, selection, menu, it, item)
if sub is not None:
item.SetSubMenu(sub)
@@ -95,7 +95,7 @@ class ContextMenu(object):
def activate(self, context, selection, i):
return None
def getSubMenu(self, context, selection, menu, i):
def getSubMenu(self, context, selection, menu, i, pitem):
return None
def getText(self, context, selection):

View File

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

View File

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

View File

@@ -22,12 +22,14 @@ import bitmapLoader
import service
from wx.lib.intctrl import IntCtrl
from gui.utils.clipboard import toClipboard, fromClipboard
from service.damagePattern import ImportError
###########################################################################
## Class DmgPatternEditorDlg
###########################################################################
class DmgPatternEditorDlg (wx.Dialog):
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
def __init__(self, parent):
wx.Dialog.__init__ (self, parent, id = wx.ID_ANY, title = u"Damage Pattern Editor", size = wx.Size( 400,240 ))
@@ -93,34 +95,37 @@ class DmgPatternEditorDlg (wx.Dialog):
self.kinbitmap = bitmapLoader.getBitmap("kinetic_big", "icons")
self.expbitmap = bitmapLoader.getBitmap("explosive_big", "icons")
dmgeditSizer = wx.FlexGridSizer(2, 4, 0, 2)
dmgeditSizer = wx.FlexGridSizer(2, 6, 0, 2)
dmgeditSizer.AddGrowableCol(0)
dmgeditSizer.AddGrowableCol(3)
dmgeditSizer.AddGrowableCol(5)
dmgeditSizer.SetFlexibleDirection(wx.BOTH)
dmgeditSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)
width = -1
defSize = wx.Size(width,-1)
self.bmpEM = wx.StaticBitmap(self, wx.ID_ANY, self.embitmap)
dmgeditSizer.Add(self.bmpEM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)
self.editEm = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize)
dmgeditSizer.Add(self.editEm, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
for i, type in enumerate(self.DAMAGE_TYPES):
bmp = wx.StaticBitmap(self, wx.ID_ANY, bitmapLoader.getBitmap("%s_big"%type, "icons"))
if i%2:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
border = 10
else:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
border = 5
self.bmpTHERM = wx.StaticBitmap(self, wx.ID_ANY, self.thermbitmap)
dmgeditSizer.Add(self.bmpTHERM, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25)
self.editThermal = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0)
dmgeditSizer.Add(self.editThermal, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
# set text edit
setattr(self, "%sEdit"%type, IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize))
setattr(self, "%sPerc"%type, wx.StaticText(self, wx.ID_ANY, u"0%"))
editObj = getattr(self, "%sEdit"%type)
self.bmpKIN = wx.StaticBitmap(self, wx.ID_ANY, self.kinbitmap)
dmgeditSizer.Add(self.bmpKIN, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)
self.editKinetic = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize)
dmgeditSizer.Add(self.editKinetic, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
dmgeditSizer.Add(bmp, 0, style, border)
dmgeditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
dmgeditSizer.Add(getattr(self, "%sPerc"%type), 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5)
self.bmpEXP = wx.StaticBitmap(self, wx.ID_ANY, self.expbitmap)
dmgeditSizer.Add(self.bmpEXP, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT, 25)
self.editExplosive = IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize, 0)
dmgeditSizer.Add(self.editExplosive, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5)
editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated)
editObj.SetLimited(True)
editObj.SetMin(0)
editObj.SetMax(99999)
contentSizer.Add(dmgeditSizer, 1, wx.EXPAND | wx.ALL, 5)
self.slfooter = wx.StaticLine(self)
@@ -129,9 +134,9 @@ class DmgPatternEditorDlg (wx.Dialog):
footerSizer = wx.BoxSizer(wx.HORIZONTAL)
perSizer = wx.BoxSizer(wx.VERTICAL)
self.stPercentages = wx.StaticText(self, wx.ID_ANY, u"")
self.stPercentages.Wrap(-1)
perSizer.Add(self.stPercentages, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
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)
@@ -167,22 +172,6 @@ class DmgPatternEditorDlg (wx.Dialog):
bsize = self.GetBestSize()
self.SetSize((-1,bsize.height))
self.editEm.SetLimited(True)
self.editThermal.SetLimited(True)
self.editKinetic.SetLimited(True)
self.editExplosive.SetLimited(True)
self.editEm.SetMin(0)
self.editThermal.SetMin(0)
self.editKinetic.SetMin(0)
self.editExplosive.SetMin(0)
self.editEm.SetMax(99999)
self.editThermal.SetMax(99999)
self.editKinetic.SetMax(99999)
self.editExplosive.SetMax(99999)
self.new.Bind(wx.EVT_BUTTON, self.newPattern)
self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
@@ -190,11 +179,6 @@ class DmgPatternEditorDlg (wx.Dialog):
self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
self.editEm.Bind(wx.EVT_TEXT, self.ValuesUpdated)
self.editThermal.Bind(wx.EVT_TEXT, self.ValuesUpdated)
self.editKinetic.Bind(wx.EVT_TEXT, self.ValuesUpdated)
self.editExplosive.Bind(wx.EVT_TEXT, self.ValuesUpdated)
self.patternChanged()
def closeEvent(self, event):
@@ -205,18 +189,13 @@ class DmgPatternEditorDlg (wx.Dialog):
return
p = self.getActivePattern()
p.emAmount = self._EM = self.editEm.GetValue()
p.thermalAmount = self._THERM = self.editThermal.GetValue()
p.kineticAmount = self._KIN = self.editKinetic.GetValue()
p.explosiveAmount = self._EXP = self.editExplosive.GetValue()
total = self._EM + self._THERM + self._KIN + self._EXP
format = "EM: %d%%, THERM: %d%%, KIN: %d%%, EXP: %d%%"
if total > 0:
ltext = format %(self._EM*100/total, self._THERM*100/total, self._KIN*100/total, self._EXP*100/total)
else:
ltext = format %(0, 0, 0, 0)
total = sum(map(lambda attr: getattr(self, "%sEdit"%attr).GetValue(), self.DAMAGE_TYPES))
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
percObj = getattr(self, "%sPerc"%type)
setattr(p, "%sAmount"%type, editObj.GetValue())
percObj.SetLabel("%.1f%%"%(float(editObj.GetValue())*100/total if total > 0 else 0))
self.stPercentages.SetLabel(ltext)
self.totSizer.Layout()
if event is not None:
@@ -225,18 +204,16 @@ class DmgPatternEditorDlg (wx.Dialog):
service.DamagePattern.getInstance().saveChanges(p)
def restrict(self):
self.editEm.Enable(False)
self.editExplosive.Enable(False)
self.editKinetic.Enable(False)
self.editThermal.Enable(False)
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):
self.editEm.Enable()
self.editExplosive.Enable()
self.editKinetic.Enable()
self.editThermal.Enable()
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.Enable()
self.rename.Enable()
self.delete.Enable()
@@ -248,6 +225,7 @@ class DmgPatternEditorDlg (wx.Dialog):
def patternChanged(self, event=None):
p = self.getActivePattern()
if p is None:
return
@@ -257,8 +235,9 @@ class DmgPatternEditorDlg (wx.Dialog):
self.unrestrict()
self.block = True
for field in ("em", "thermal", "kinetic", "explosive"):
edit = getattr(self, "edit%s" % field.capitalize())
for field in self.DAMAGE_TYPES:
edit = getattr(self, "%sEdit" % field)
amount = getattr(p, "%sAmount" % field)
edit.SetValue(amount)
@@ -271,6 +250,13 @@ class DmgPatternEditorDlg (wx.Dialog):
self.choices.append(p)
id = self.ccDmgPattern.Append(p.name)
self.ccDmgPattern.SetSelection(id)
self.restrict()
# reset values
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.SetValue(0)
self.btnSave.SetLabel("Create")
self.renamePattern()
@@ -296,16 +282,16 @@ class DmgPatternEditorDlg (wx.Dialog):
def processRename(self, event):
newName = self.namePicker.GetLineText(0)
self.stPercentages.SetLabel("")
self.stNotice.SetLabel("")
p = self.getActivePattern()
for pattern in self.choices:
if pattern.name == newName and p != pattern:
self.stPercentages.SetLabel("Name already used, please pick another")
self.stNotice.SetLabel("Name already used, please choose another")
return
if newName == "":
self.stPercentages.SetLabel("Invalid name.")
self.stNotice.SetLabel("Invalid name.")
return
sDP = service.DamagePattern.getInstance()
@@ -349,16 +335,42 @@ class DmgPatternEditorDlg (wx.Dialog):
def __del__( self ):
pass
def updateChoices(self):
"Gathers list of patterns and updates choice selections"
sDP = service.DamagePattern.getInstance()
self.choices = sDP.getDamagePatternList()
for dp in self.choices:
if dp.name == "Selected Ammo": # don't include this special butterfly
self.choices.remove(dp)
# Sort the remaining list and continue on
self.choices.sort(key=lambda p: p.name)
self.ccDmgPattern.Clear()
for choice in map(lambda p: p.name, self.choices):
self.ccDmgPattern.Append(choice)
self.ccDmgPattern.SetSelection(0)
self.patternChanged()
def importPatterns(self, event):
text = fromClipboard()
if text:
sDP = service.DamagePattern.getInstance()
sDP.importPatterns(text)
self.stPercentages.SetLabel("Patterns imported from clipboard")
try:
sDP.importPatterns(text)
self.stNotice.SetLabel("Patterns successfully imported from clipboard")
except service.damagePattern.ImportError, e:
self.stNotice.SetLabel(str(e))
except Exception, e:
self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
finally:
self.updateChoices()
else:
self.stPercentages.SetLabel("Could not import from clipboard")
self.stNotice.SetLabel("Could not import from clipboard")
def exportPatterns(self, event):
sDP = service.DamagePattern.getInstance()
toClipboard( sDP.exportPatterns() )
self.stPercentages.SetLabel("Patterns exported to clipboard")
self.stNotice.SetLabel("Patterns exported to clipboard")

407
gui/resistsEditor.py Normal file
View File

@@ -0,0 +1,407 @@
#===============================================================================
# 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 <http://www.gnu.org/licenses/>.
#===============================================================================
import wx
import bitmapLoader
import service
from gui.utils.clipboard import toClipboard, fromClipboard
from service.targetResists import ImportError
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):
'''
Event that is fired when resists values change. Iterates through all
resist edit fields. If blank, sets it to 0.0. If it is not a proper
decimal value, sets text color to red and refuses to save changes until
issue is resolved
'''
if self.block:
return
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.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.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: # Refresh for color changes to take effect immediately
self.Refresh()
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):
"Event fired when user selects pattern. Can also be called from script"
p = self.getActivePattern()
if p is None:
# This happens when there are no patterns in the DB. As such, force
# user to create one first or exit dlg.
self.newPattern(None)
return
self.block = True
# Set new values
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):
'''
Simply does new-pattern specifics: replaces label on button, restricts,
and resets values to default. Hands off to the rename function for
further handling.
'''
self.btnSave.SetLabel("Create")
self.restrict()
# reset values
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.ChangeValue("0.0")
editObj.SetForegroundColour(wx.NullColor)
self.Refresh()
self.renamePattern()
def renamePattern(self, event=None):
"Changes layout to facilitate naming a pattern"
self.ccResists.Hide()
self.namePicker.Show()
self.headerSizer.Replace(self.ccResists, self.namePicker)
self.namePicker.SetFocus()
if event is not None: # Rename mode
self.btnSave.SetLabel("Rename")
self.namePicker.SetValue(self.getActivePattern().name)
else: # Create mode
self.namePicker.SetValue("")
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):
'''
Processes rename event (which can be new or old patterns). If new
pattern, creates it; if old, selects it. if checks are valid, rename
saves pattern to DB.
Also resets to default layout and unrestricts.
'''
newName = self.namePicker.GetLineText(0)
self.stNotice.SetLabel("")
if newName == "":
self.stNotice.SetLabel("Invalid name")
return
sTR = service.TargetResists.getInstance()
if event.EventObject.Label == "Create":
p = sTR.newPattern()
else:
# we are renaming, so get the current selection
p = self.getActivePattern()
# test for patterns of the same name
for pattern in self.choices:
if pattern.name == newName and p != pattern:
self.stNotice.SetLabel("Name already used, please choose another")
return
# rename regardless of new or rename
sTR.renamePattern(p, newName)
self.updateChoices(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.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 updateChoices(self, select=None):
"Gathers list of patterns and updates choice selections"
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.Clear()
for i, choice in enumerate(map(lambda p: p.name, self.choices)):
self.ccResists.Append(choice)
if select is not None and choice == select:
self.ccResists.SetSelection(i)
if select is None:
self.ccResists.SetSelection(0)
self.patternChanged()
def importPatterns(self, event):
"Event fired when import from clipboard button is clicked"
text = fromClipboard()
if text:
sTR = service.TargetResists.getInstance()
try:
sTR.importPatterns(text)
self.stNotice.SetLabel("Patterns successfully imported from clipboard")
except service.targetResists.ImportError, e:
self.stNotice.SetLabel(str(e))
except Exception, e:
self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
finally:
self.updateChoices()
else:
self.stNotice.SetLabel("Could not import from clipboard")
def exportPatterns(self, event):
"Event fired when export to clipboard button is clicked"
sTR = service.TargetResists.getInstance()
toClipboard( sTR.exportPatterns() )
self.stNotice.SetLabel("Patterns exported to clipboard")

View File

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

View File

@@ -21,6 +21,9 @@ import eos.db
import eos.types
import copy
class ImportError(Exception):
pass
class DamagePattern():
instance = None
@classmethod
@@ -68,17 +71,21 @@ class DamagePattern():
current = self.getDamagePatternList()
for pattern in current:
lookup[pattern.name] = pattern
try:
imports = eos.types.DamagePattern.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
imports, num = eos.types.DamagePattern.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()
lenImports = len(imports)
if lenImports == 0:
raise ImportError("No patterns found for import")
if lenImports != num:
raise ImportError("%d patterns imported from clipboard; %d had errors"%(num, num-lenImports))
def exportPatterns(self):
patterns = self.getDamagePatternList()

View File

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

87
service/targetResists.py Normal file
View File

@@ -0,0 +1,87 @@
#===============================================================================
# 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 <http://www.gnu.org/licenses/>.
#===============================================================================
import eos.db
import eos.types
import copy
class ImportError(Exception):
pass
class TargetResists():
instance = None
@classmethod
def getInstance(cls):
if cls.instance is None:
cls.instance = TargetResists()
return cls.instance
def getTargetResistsList(self):
return eos.db.getTargetResistsList()
def getTargetResists(self, name):
return eos.db.getTargetResists(name)
def newPattern(self):
p = eos.types.TargetResists(0.0, 0.0, 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
imports, num = 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()
lenImports = len(imports)
if lenImports == 0:
raise ImportError("No patterns found for import")
if lenImports != num:
raise ImportError("%d patterns imported from clipboard; %d had errors"%(num, num-lenImports))
def exportPatterns(self):
patterns = self.getTargetResistsList()
patterns.sort(key=lambda p: p.name)
return eos.types.TargetResists.exportPatterns(*patterns)