""" This is terribly written but still it should give you a broad idea of whether or not a gun is better than the other or not. This keeps track of: * Damage * Fire rate * Reload time * Magazine * Elemental damage * Damage type (vs flesh, shield, armor) * Playthrough (by default the second) * Optionally accuracy (but not by default) * Optionally recoil (but not by default) Example execution: ================================= Damage:3316 Accuracy:95.7 Fire rate:8.7 Reload time:2.7 Magazine:25 Element (empty or f'ire, s'hock, c'orrosive, e'xplosive): s Elemental DPS:3191.1 Elemental chance:10 DPS: Damage( 17,892.65 Flesh, 17,892.65 Armor, 44,731.62 Shield) ================================= Damage:5255 Accuracy:91 Fire rate:5.5 Reload time:2.3 Magazine:24 Element (empty or f'ire, s'hock, c'orrosive, e'xplosive): s Elemental DPS:2823.9 Elemental chance:12.9 DPS: Damage( 21,022.29 Flesh, 21,022.29 Armor, 52,555.74 Shield) ================================= """ # The dumbest possible approach to measuring DPS from __future__ import division import collections from random import random from operator import itemgetter elementLength = {"fire": 8, "shock": 2, "corrosive": 8, "slag": 0, #8, "explosive": 0, None: 0} class Damage(): def __init__(self, flesh = 0, armor = 0, shield = 0): self.flesh = flesh self.armor = armor self.shield = shield def __iter__(self): yield self.flesh yield self.armor yield self.shield def __str__(self): return "Damage({0.flesh:10,.2f} Flesh, {0.armor:10,.2f} Armor, {0.shield:10,.2f} Shield)".format(self) def __add__(this, that): return Damage(*(a+b for a, b in zip(this, that))) def __mul__(this, that): return Damage(*(a*that for a in this)) def __truediv__(this, that): return Damage(*(a/that for a in this)) def add(self, value, element, playthrough): damage = damageMults[element][playthrough] * value self.flesh += damage.flesh self.armor += damage.armor self.shield += damage.shield damageMults = { #NESTED DICTIONARIES HO! None: {1: Damage(1 , 0.8 , 1), 2: Damage(1 , 0.8 , 1 )}, "slag": {1: Damage(1 , 0.8 , 1), 2: Damage(1 , 0.8 , 1 )}, "fire": {1: Damage(1.5, 0.75, 0.75), 2: Damage(1.75, 0.4 , 0.4)}, "shock": {1: Damage(1 , 1 , 2), 2: Damage(1 , 1 , 2.5)}, "corrosive": {1: Damage(0.9, 1.5, 0.75), 2: Damage(0.6 , 1.75, 0.4)}, "explode": {1: Damage(1 , 1 , 0.8), 2: Damage(1 , 1 , 0.8)} } Weapon = collections.namedtuple("Weapon", ["damage", "pelletCount", "accuracy", "fireRate", "reloadTime", "magazine", "element", "elementChance", "elementDPS"]) ##BonusStats = collections.namedtuple("BonusStats", ## ["health", ## "shield", ## "shieldDelay", ## "shieldRate", ## "melee", ## "grenade", ## "accuracy", ## "damage", ## "rate", ## "recoil", ## "reload", ## "elementChance", ## "elementDamage", ## "critical"]) #OH MY FUCKING GOD class BonusStats(tuple): 'BonusStats(health, shield, shieldDelay, shieldRate, melee, grenade, accuracy, damage, rate, recoil, reload, elementChance, elementDamage, critical)' __slots__ = () _fields = ('health', 'shield', 'shieldDelay', 'shieldRate', 'melee', 'grenade', 'accuracy', 'damage', 'rate', 'recoil', 'reload', 'elementChance', 'elementDamage', 'critical') def __new__(_cls, health, shield, shieldDelay, shieldRate, melee, grenade, accuracy, damage, rate, recoil, reload, elementChance, elementDamage, critical): 'Create new instance of BonusStats(health, shield, shieldDelay, shieldRate, melee, grenade, accuracy, damage, rate, recoil, reload, elementChance, elementDamage, critical)' return tuple.__new__(_cls, (health, shield, shieldDelay, shieldRate, melee, grenade, accuracy, damage, rate, recoil, reload, elementChance, elementDamage, critical)) @classmethod def _make(cls, iterable, new=tuple.__new__, len=len): 'Make a new BonusStats object from a sequence or iterable' result = new(cls, iterable) if len(result) != 14: raise TypeError('Expected 14 arguments, got %d' % len(result)) return result def __repr__(self): 'Return a nicely formatted representation string' return 'BonusStats(health=%r, shield=%r, shieldDelay=%r, shieldRate=%r, melee=%r, grenade=%r, accuracy=%r, damage=%r, rate=%r, recoil=%r, reload=%r, elementChance=%r, elementDamage=%r, critical=%r)' % self def _asdict(self): 'Return a new OrderedDict which maps field names to their values' return OrderedDict(zip(self._fields, self)) def _replace(_self, **kwds): 'Return a new BonusStats object replacing specified fields with new values' result = _self._make(map(kwds.pop, ('health', 'shield', 'shieldDelay', 'shieldRate', 'melee', 'grenade', 'accuracy', 'damage', 'rate', 'recoil', 'reload', 'elementChance', 'elementDamage', 'critical'), _self)) if kwds: raise ValueError('Got unexpected field names: %r' % kwds.keys()) return result def __getnewargs__(self): 'Return self as a plain tuple. Used by copy and pickle.' return tuple(self) def _itemgetter(x): def g(obj): return 1 + obj[x]/100 return g health = property(_itemgetter(0), doc='Alias for field number 0') shield = property(_itemgetter(1), doc='Alias for field number 1') shieldDelay = property(_itemgetter(2), doc='Alias for field number 2') shieldRate = property(_itemgetter(3), doc='Alias for field number 3') melee = property(_itemgetter(4), doc='Alias for field number 4') grenade = property(_itemgetter(5), doc='Alias for field number 5') accuracy = property(_itemgetter(6), doc='Alias for field number 6') damage = property(_itemgetter(7), doc='Alias for field number 7') rate = property(_itemgetter(8), doc='Alias for field number 8') recoil = property(_itemgetter(9), doc='Alias for field number 9') reload = property(_itemgetter(10), doc='Alias for field number 10') elementChance = property(_itemgetter(11), doc='Alias for field number 11') elementDamage = property(_itemgetter(12), doc='Alias for field number 12') critical = property(_itemgetter(13), doc='Alias for field number 13') zeroStats = BonusStats(0,0,0,0,0,0,0,0,0,0,0,0,0,0) #Example stormingVexation = Weapon(1140, 1, 92.9, 8.0, 2.5, 27, "shock", 1620.5, 18.7) myStats = BonusStats(7.6, 7.6, 6.8, 5.2, 5.6, 3.3, 6.8, 7.6, 7.6, 7.6, 7.2, 7.2, 4.3, 6.8) def dps(weapon, playthrough = 2, bonusStats = zeroStats, referenceTime = 60, accuracyAdjusting = lambda x: 1, # lambda x: x**(1/3), accountForRecoilInVastlyBogusWays = False): time = 0 damage = Damage(0,0,0) ammoCount = weapon.magazine recoilMalus = 0 while time <= referenceTime: time += 1/(weapon.fireRate * bonusStats.rate) for i in xrange(weapon.pelletCount): if random() < accuracyAdjusting(weapon.accuracy/100 * bonusStats.accuracy + recoilMalus): damage.add(weapon.damage * bonusStats.damage, weapon.element, playthrough) if weapon.element: if random() < weapon.elementChance/100 * bonusStats.elementChance: damage.add(weapon.elementDPS * bonusStats.elementDamage * elementLength[weapon.element], weapon.element, playthrough) #are elemental ticks actually affected as well? ammoCount -= 1 if accountForRecoilInVastlyBogusWays: recoilMalus -= 0.01 * 1/bonusStats.recoil if ammoCount == 0: time += weapon.reloadTime * bonusStats.reload ammoCount = weapon.magazine recoilMalus = 0 return damage/time # Weapon = collections.namedtuple("Weapon", # ["damage", # "pelletCount", # "accuracy", # "fireRate", # "reloadTime", # "magazine", # "element", # "elementChance", # "elementDPS"]) def inputWeapon(): elements = {"": None, "f": "fire", "s": "shock", "c": "corrosive", "g": "slag", "e": "explode"} dmg = raw_input("Damage:") if "x" in dmg: dmg, pellets = dmg.split("x") pellets = int(pellets) else: pellets = 1 dmg = float(dmg) accuracy = float(raw_input("Accuracy:")) fireRate = float(raw_input("Fire rate:")) reloadTime = float(raw_input("Reload time:")) magazine = int(raw_input("Magazine:")) element = elements[raw_input("Element (empty or f'ire, s'hock, c'orrosive, e'xplosive): ")] if element in ("fire", "shock", "corrosive"): elDPS = float(raw_input("Elemental DPS:")) chance = float(raw_input("Elemental chance:")) else: elDPS, chance = 0, 0 return Weapon(dmg, pellets, accuracy, fireRate, reloadTime, magazine, element, chance, elDPS) while True: print "=================================" wep = inputWeapon() print "" print "DPS: ", dps(wep)