Merge pull request #168 from blitzmann/161-effDps
Implement effective DPS
This commit is contained in:
@@ -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:":
|
||||
|
||||
@@ -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;")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
__all__ = ["character", "fit", "module", "user", "skill", "price",
|
||||
"booster", "drone", "implant", "fleet", "damagePattern",
|
||||
"miscData"]
|
||||
"miscData", "targetResists"]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
35
eos/db/saveddata/targetResists.py
Normal file
35
eos/db/saveddata/targetResists.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
84
eos/saveddata/targetResists.py
Normal file
84
eos/saveddata/targetResists.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user