Skip to content

Commit

Permalink
Merge pull request pyfa-org#168 from blitzmann/161-effDps
Browse files Browse the repository at this point in the history
Implement effective DPS
  • Loading branch information
blitzmann committed Sep 19, 2014
2 parents 6d15645 + c903dff commit b5f2df5
Show file tree
Hide file tree
Showing 28 changed files with 1,077 additions and 153 deletions.
2 changes: 1 addition & 1 deletion eos/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ReadOnlyException(Exception):
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:":
Expand Down
17 changes: 17 additions & 0 deletions eos/db/migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;")
2 changes: 1 addition & 1 deletion eos/db/saveddata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__all__ = ["character", "fit", "module", "user", "skill", "price",
"booster", "drone", "implant", "fleet", "damagePattern",
"miscData"]
"miscData", "targetResists"]
7 changes: 5 additions & 2 deletions eos/db/saveddata/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@
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),
Expand All @@ -38,7 +39,8 @@
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),
Expand All @@ -64,6 +66,7 @@
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,
Expand Down
30 changes: 27 additions & 3 deletions eos/db/saveddata/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down Expand Up @@ -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):
Expand All @@ -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")
Expand All @@ -361,15 +385,15 @@ def getSquadsIDsWithFitID(fitID):
return squads
else:
raise TypeError("Need integer as argument")

def getProjectedFits(fitID):
if isinstance(fitID, int):
with sd_lock:
filter = and_(projectedFits_table.c.sourceID == fitID, Fit.ID == projectedFits_table.c.victimID)
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:
Expand Down
35 changes: 35 additions & 0 deletions eos/db/saveddata/targetResists.py
Original file line number Diff line number Diff line change
@@ -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)
42 changes: 30 additions & 12 deletions eos/saveddata/damagePattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,26 +69,44 @@ def effectivify(self, fit, amount, type):
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)

Expand Down
9 changes: 7 additions & 2 deletions eos/saveddata/drone.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
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):
Expand Down Expand Up @@ -111,6 +111,9 @@ def hasAmmo(self):

@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:
Expand All @@ -121,7 +124,9 @@ def dps(self):
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:
Expand Down
16 changes: 14 additions & 2 deletions eos/saveddata/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ def build(self):
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
Expand Down Expand Up @@ -811,12 +822,12 @@ def calculateWeaponStats(self):
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
Expand All @@ -838,6 +849,7 @@ def __deepcopy__(self, memo):
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:
Expand Down
42 changes: 18 additions & 24 deletions eos/saveddata/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -308,29 +308,23 @@ def charge(self, charge):

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

Expand All @@ -354,11 +348,11 @@ def miningStats(self):

@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):
Expand Down
Loading

0 comments on commit b5f2df5

Please sign in to comment.