Skip to content

Instantly share code, notes, and snippets.

@badp
Created December 17, 2012 15:13
Show Gist options
  • Select an option

  • Save badp/4319017 to your computer and use it in GitHub Desktop.

Select an option

Save badp/4319017 to your computer and use it in GitHub Desktop.

Revisions

  1. badp revised this gist Dec 17, 2012. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion borderlands2dps.py
    Original file line number Diff line number Diff line change
    @@ -230,7 +230,7 @@ def inputWeapon():
    dmg = raw_input("Damage:")
    if "x" in dmg:
    dmg, pellets = dmg.split("x")
    pellets = float(pellets)
    pellets = int(pellets)
    else:
    pellets = 1
    dmg = float(dmg)
  2. badp renamed this gist Dec 17, 2012. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. badp created this gist Dec 17, 2012.
    253 changes: 253 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,253 @@
    """
    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 = float(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)