{"id":24754,"date":"2014-10-28T09:51:27","date_gmt":"2014-10-28T14:51:27","guid":{"rendered":"http:\/\/www.shamusyoung.com\/twentysidedtale\/?p=24754"},"modified":"2014-10-28T09:51:27","modified_gmt":"2014-10-28T14:51:27","slug":"borderlands-badass-ranks","status":"publish","type":"post","link":"https:\/\/www.shamusyoung.com\/twentysidedtale\/?p=24754","title":{"rendered":"Borderlands Badass Ranks"},"content":{"rendered":"<p>The Borderlands games have this thing called &#8220;Badass ranks&#8221;. It&#8217;s this sort of meta-leveling system that&#8217;s not tied to any specific character, but is instead based on some global state<span class='snote' title='1'>Actually, in the first one it works a little different, but whatever. For now let&#8217;s focus on the Borderlands 2 and Pre-Sequel.<\/span>. You complete goals, the goals give you badass points, the points fill a leveling bar, and when it fills up you get a single badass token. These tokens can be spent on really, really small upgrades like &#8220;Plus a tenth of a percent more damage with guns&#8221;. But these upgrades apply to all your characters, now and in the future.<\/p>\n<p>The goals are typical achievement-bait type stuff: Kill X people with fire damage. Open X loot chests. Loot X items of a given rarity. Sell X items. Kill X of creature Y with melee attacks. Get X headshots. Do X sidequests. Deal X points of damage with weapon Y. And so on.<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/badass1.jpg' class='insetimage' width='600' alt='This is Athena from Pre-Sequel. (Best character.) The bonus stats you see on the left apply to all the characters I create. The game lets you disable these if you want, presumably to allow serious players to set up &#8220;fair&#8221; duels.' title='This is Athena from Pre-Sequel. (Best character.) The bonus stats you see on the left apply to all the characters I create. The game lets you disable these if you want, presumably to allow serious players to set up &#8220;fair&#8221; duels.'\/><\/td><\/tr><tr><td class='insetcaption'>This is Athena from Pre-Sequel. (Best character.) The bonus stats you see on the left apply to all the characters I create. The game lets you disable these if you want, presumably to allow serious players to set up &#8220;fair&#8221; duels.<\/td><\/tr><\/table><\/p>\n<p>There are numerous goals, to the point where you&#8217;ll be hitting new ones every couple of minutes. If you&#8217;re looking to maximize the number of badass tokens you earn, then it&#8217;s best to avoid getting stuck in a rut with one favorite character, weapon, and playstyle. Use all the weapons, fight all the foes, use vehicles, use melee, and generally mix things up.<\/p>\n<p><!--more-->There&#8217;s no limit on how many badass tokens you can earn, although they do become harder (slower) to earn over time. This means that your first character will always be your weakest. If you go through the game with multiple characters and earn tons of tokens, then later on your characters will have all these bonuses that weren&#8217;t available to you the first time you fired up the game. <\/p>\n<p>The bonuses are small, but they stack and they impact nearly every aspect of your character. An extra one percent gun damage is nothing. But what if you&#8217;re shooting one percent faster, hitting one percent more accurately, getting one percent more criticals, have one percent more health, AND doing one percent more damage? That&#8217;s probably still a minor advantage, but I&#8217;m betting that by the time you&#8217;ve got a 5% to all your stats you&#8217;re a lot more than just &#8220;5% stronger&#8221;. <\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/badass2.jpg' class='insetimage' width='600' alt='Maya from Borderlands 2. Here you can see I get to choose from five possible bonuses: Elemental Effect CHANCE, Melee Damage, Critical Hit Damage, Elemental Effect Damage, and Gun Accuracy. (These probably yield a tenth of a percent bonus.) Below that you can see the sum of all the bonuses I&#8217;ve unlocked so far.' title='Maya from Borderlands 2. Here you can see I get to choose from five possible bonuses: Elemental Effect CHANCE, Melee Damage, Critical Hit Damage, Elemental Effect Damage, and Gun Accuracy. (These probably yield a tenth of a percent bonus.) Below that you can see the sum of all the bonuses I&#8217;ve unlocked so far.'\/><\/td><\/tr><tr><td class='insetcaption'>Maya from Borderlands 2. Here you can see I get to choose from five possible bonuses: Elemental Effect CHANCE, Melee Damage, Critical Hit Damage, Elemental Effect Damage, and Gun Accuracy. (These probably yield a tenth of a percent bonus.) Below that you can see the sum of all the bonuses I&#8217;ve unlocked so far.<\/td><\/tr><\/table><\/p>\n<p>It&#8217;s also worth noting that you can&#8217;t directly control what stats get boosted. When you go to spend a token on upgrades, you have to choose ONE of five options out of the dozens of possible stats. So if you start pouring everything into (say) shields, the game will stop offering shields as one of the possible upgrades until you put some points in elsewhere. It&#8217;s basically a system designed to blunt attempts at min-maxing. <\/p>\n<p>In any case, the upgrades make you stronger. But&#8230; <strong>how much<\/strong> stronger? That&#8217;s a really complex question. Short of looking at the Borderlands game code there&#8217;s no way to be sure. But I decided to write a little program to simulate combat to see how much of a benefit &#8220;5% to everything&#8221; might be. <\/p>\n<p>This all happens in a terminal window, no graphics. (Sorry.) We&#8217;re simulating two evenly-matched combatants. The two characters shoot at each other. The moment one drops dead, the victim instantly respawns with full health and a reloaded weapon. We keep track of kills and the simulation ends after 1,000 deaths.<\/p>\n<p>(Upon reflection, it might have been better to have two theoretical players gun down NPC mooks over a fixed period of time, and score them based on who slaughtered the most mooks.)<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/badass4.jpg' class='insetimage' width='600' alt='Axton kinda ruined the game for me. His special power was a turret with massive hitpoints and good damage output that instantly grabbed all aggro. It was basically a &#8220;get out of dying, free&#8221; button. It felt far more powerful than the other special abilities, but was also kinda boring to use.' title='Axton kinda ruined the game for me. His special power was a turret with massive hitpoints and good damage output that instantly grabbed all aggro. It was basically a &#8220;get out of dying, free&#8221; button. It felt far more powerful than the other special abilities, but was also kinda boring to use.'\/><\/td><\/tr><tr><td class='insetcaption'>Axton kinda ruined the game for me. His special power was a turret with massive hitpoints and good damage output that instantly grabbed all aggro. It was basically a &#8220;get out of dying, free&#8221; button. It felt far more powerful than the other special abilities, but was also kinda boring to use.<\/td><\/tr><\/table><\/p>\n<p>As all projects, this one get quickly mired in vexing questions that weren&#8217;t obvious at first: <\/p>\n<ol>\n<li>What kind of weapons are we simulating? In a Borderlands shotgun fight, the guns do MASSIVE (possibly insta-kill) damage but take ages to reload. If we&#8217;re using an SMG, then the bullets do tiny damage. With one weapon the bonus damage is meaningless (since every hit is a kill) while the other weapon magnifies the importance of bonus damage.<\/li>\n<li>In Borderlands, weapons have this accuracy stat that controls how wide their cone of possible bullet trajectories is. This is supremely important in a sniper rifle battle at long distances, and completely worthless if we&#8217;re using rockets at point-blank range.\n<li>If fights are high damage then bonuses to reload speeds and magazine sizes are useless, since combatants will drop dead long before they empty their weapon.\n<li>How does a bonus to &#8220;refire rate&#8221; work? If a weapon had 100% bonus to fire rate, would that make it shoot twice as fast, or would that reduce the cooldown to zero, effectively making a weapon that should fire &#8220;infinitely&#8221; fast? I&#8217;ll assume the former, since the latter just makes no sense. However, there&#8217;s no telling what&#8217;s going on under the hood in Borderlands. The tooltips were designed for brevity and simplicity, not precision. They exist so you can eyeball the difference between two items at a glance.\n<\/ol>\n<p>Basically, this test is meaningless because I can coerce any result I want. I can set up a fight where 5% bonus can change the entire outcome or I can set up a fight where the 5% bonus does almost nothing. <\/p>\n<p>I try to split the difference and make sure all stats are useful. We&#8217;ll assume a fight at medium distance with a low damage weapon with a moderate rate of fire. <\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/badass3.jpg' class='insetimage' width='600' alt='Gaige from Borderlands 2. You can see I&#8217;ll earn a Badass rank for killing ten midgets. The next tier will probably unlock at something like 100 midgets, then 1000, etc.' title='Gaige from Borderlands 2. You can see I&#8217;ll earn a Badass rank for killing ten midgets. The next tier will probably unlock at something like 100 midgets, then 1000, etc.'\/><\/td><\/tr><tr><td class='insetcaption'>Gaige from Borderlands 2. You can see I&#8217;ll earn a Badass rank for killing ten midgets. The next tier will probably unlock at something like 100 midgets, then 1000, etc.<\/td><\/tr><\/table><\/p>\n<p>I&#8217;ve got two combatants, which for the purposes of this exercise I&#8217;ve named &#8220;Player&#8221; and &#8220;Mook&#8221;. With no bonus, they each have 100 hitpoints, have a 25% chance to hit, their guns fire every 100 time-units<span class='snote' title='2'>Generic time units. Not mapped to real-world time in any meaningful way. I don&#8217;t want to actually sit here and wait ten minutes for the contest to play out.<\/span> and deal 15 damage. Their default guns hold 20 bullets and take 500 time units to reload<span class='snote' title='3'>Effectively making you miss out on five shots every time you reload.<\/span>. <\/p>\n<p>This isn&#8217;t simulating a player duel, because those things happen one at a time and not back-to-back. And we&#8217;re not simulating a fight between players and foes, because those aren&#8217;t evenly matched. Instead we&#8217;re trying to measure the strength of a boosted player against the strength of an un-boosted player. We&#8217;re not going to mess around with stuff like shields, melee attacks, grenades, and elemental damage. So don&#8217;t think of this as &#8220;simulated Borderlands&#8221;. It&#8217;s more an abstract test to see how powerful it is to have a small percent boost across all stats in a game <em>like<\/em> Borderlands. <\/p>\n<p>The result? Really unsatisfying. <\/p>\n<p>With no bonus, the match comes out perfectly evenly, with each combatant getting 500 kills.<\/p>\n<p>If I give Player a 1% bonus, then Player scores 522 kills versus the 478 kills of Mook. <\/p>\n<p>With a 3% bonus, then the match comes out 553 to 447. <\/p>\n<p>With a 5% bonus, then the match comes out 584 to 416. <\/p>\n<p>Actually, let&#8217;s just put it on a chart:<\/p>\n<p><table width='600'  cellpadding='0' cellspacing='0' border='0' align='center'><tr><td><img src='https:\/\/www.shamusyoung.com\/twentysidedtale\/images\/badass_chart.jpg' class='insetimage' width='600' alt='Bo-ring. :(' title='Bo-ring. :('\/><\/td><\/tr><\/table><\/p>\n<p>I was expecting some kind of curve. Like, maybe early upgrades were useless but then scores would ramp up? Or maybe you&#8217;d get a lot of benefit from early bonuses, but their benefit would taper off? But no. We get a linear, shallow progression. At 0% bonus the two are evenly matched, and at a 10% bonus Player gets two-thirds of the kills. <\/p>\n<p>It should be noted that an across-the-board ten percent bonus is pretty hard to achieve. I&#8217;ve put about 300 hours into Borderlands 2, and I have 10% bonus in just a couple of stats. <\/p>\n<p>So that&#8217;s the experiment. This isn&#8217;t remotely definitive, but my guess is that a better test would still result in a similar outcome and a similar chart. My gut tells me that the slope might change slightly, but we wouldn&#8217;t see any sort of curve emerge. <\/p>\n<p>But in case you want to have a go at it yourself, here&#8217;s the source. It&#8217;s only 200 lines of C++ code, and a quarter of that is the silly random number generator. <\/p>\n<div style=\"height:300px;overflow:scroll;\">\n<pre lang=\"c\" line=\"1\">\r\n#include <memory.h>\r\n#include <stdio.h>\r\n\r\n\/*-----------------------------------------------------------------------------\r\nThe Mersenne Twister by Matsumoto and Nishimura. Our random number generator.\r\n-----------------------------------------------------------------------------*\/\r\n\r\n#define LOWER_MASK            0x7fffffff \r\n#define M                     397\r\n#define MATRIX_A              0x9908b0df \r\n#define N                     624\r\n#define TEMPERING_MASK_B      0x9d2c5680\r\n#define TEMPERING_MASK_C      0xefc60000\r\n#define TEMPERING_SHIFT_L(y)  (y >> 18)\r\n#define TEMPERING_SHIFT_S(y)  (y << 7)\r\n#define TEMPERING_SHIFT_T(y)  (y << 15)\r\n#define TEMPERING_SHIFT_U(y)  (y >> 11)\r\n#define UPPER_MASK            0x80000000 \r\n\r\nstatic int              k = 1;\r\nstatic unsigned long    mag01[2] = {0x0, MATRIX_A};\r\nstatic unsigned long    ptgfsr[N];\r\n\r\nvoid RandomInit (unsigned long seed)\r\n{\r\n  mag01[0] = 0;\r\n  mag01[1] = MATRIX_A;\r\n  ptgfsr[0] = seed;\r\n  for (k = 1; k < N; k++)\r\n    ptgfsr[k] = 69069 * ptgfsr[k - 1];\r\n  k = 1;\r\n}\r\n\r\nunsigned long RandomVal (void)\r\n{\r\n\r\n  int\t\t            kk;\r\n  unsigned long\t    y; \r\n  \r\n  if (k == N) {\r\n    for (kk = 0; kk < N - M; kk++) {\r\n      y = (ptgfsr[kk] &#038; UPPER_MASK) | (ptgfsr[kk + 1] &#038; LOWER_MASK);\r\n      ptgfsr[kk] = ptgfsr[kk + M] ^ (y >> 1) ^ mag01[y & 0x1];\r\n      }\r\n    for (; kk < N - 1; kk++) {\r\n      y = (ptgfsr[kk] &#038; UPPER_MASK) | (ptgfsr[kk + 1] &#038; LOWER_MASK);\r\n      ptgfsr[kk] = ptgfsr[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1];\r\n      }\r\n    y = (ptgfsr[N - 1] & UPPER_MASK) | (ptgfsr[0] & LOWER_MASK);\r\n    ptgfsr[N - 1] = ptgfsr[M - 1] ^ (y >> 1) ^ mag01[y & 0x1];\r\n    k = 0;\r\n  }\r\n  y = ptgfsr[k++];\r\n  y ^= TEMPERING_SHIFT_U (y);\r\n  y ^= TEMPERING_SHIFT_S (y) & TEMPERING_MASK_B;\r\n  y ^= TEMPERING_SHIFT_T (y) & TEMPERING_MASK_C;\r\n  return y ^= TEMPERING_SHIFT_L (y);\r\n}\r\n\r\n\/*-----------------------------------------------------------------------------\r\nWe can really change the outcome of the sim by altering these values.\r\n-----------------------------------------------------------------------------*\/\r\n\r\n#define BASE_HITPOINTS    1000\r\n#define BASE_HIT_CHANCE   25\r\n#define BASE_CRIT_CHANCE  10\r\n#define BASE_DAMAGE       15\r\n#define CRITICAL_DAMAGE   1.0f\r\n#define REFIRE_TIME       100\r\n#define RELOAD_TIME       500\r\n#define MAG_SIZE          20\r\n\r\n\/*-----------------------------------------------------------------------------\r\nOur class for enacting the fight.\r\n-----------------------------------------------------------------------------*\/\r\n\r\nclass Fighter\r\n{\r\nprivate:\r\n  float     _hitpoints;\r\n  int       _cooldown;\r\n  int       _bullets;\r\n  int       _full_mag;\r\n  int       _reload;\r\n  float     _bonus;\r\n  int       _next_shot;\r\n  char      _bar[21];\r\n\r\npublic:\r\n  float     Health () { return _hitpoints; }\r\n  float     BonusPercent () { return _bonus * 0.01f; }\r\n  void      Hit (float damage)  {    _hitpoints -= damage;   }\r\n  bool      Dead () { return _hitpoints <= 0.0f; }\r\n\r\n  \/\/A status bar showing what this Fighter is doing. Fixed length for easy console viewing.\r\n  char*     Bar () {\r\n    if (_reload) \r\n      sprintf (_bar, \"%3d [reloading] %c\", (int)_hitpoints, _cooldown <= 0 ? 'x' : ' ');\r\n    else \r\n      sprintf (_bar, \"%3d [%3d \/ %3d] %c\", (int)_hitpoints, _bullets, _full_mag, _cooldown <= 0 ? 'x' : ' ');\r\n    return _bar;\r\n  }\r\n\r\n  void      Respawn ()\r\n  {\r\n    _hitpoints = BASE_HITPOINTS + (BASE_HITPOINTS * BonusPercent ());\r\n    _bullets = _full_mag;\r\n    _reload = 0;\r\n    _next_shot = 0;\r\n    _cooldown = 0;\r\n  }\r\n\r\n  void      Spawn (float bonus=0)\r\n  {\r\n    _bonus = bonus;\r\n    _full_mag = MAG_SIZE + (int)(MAG_SIZE * BonusPercent ());\r\n    Respawn ();\r\n  }\r\n\r\n  float   Shoot ()\r\n  {\r\n    int   roll;\r\n    float damage;\r\n\r\n    \/\/Do we need to reload?\r\n    if (!_reload &#038;&#038; _bullets < 1) {\r\n      _reload = RELOAD_TIME - (int)(RELOAD_TIME * BonusPercent ());\r\n    }\r\n    \/\/Are we reloading?\r\n    if (_reload) {\r\n      _reload--;\r\n      if (_reload == 0) {\r\n        _bullets = _full_mag;\r\n        _cooldown = 0;\r\n      }\r\n      return 0.0f;\r\n    }\r\n    \/\/Are we ready to fire?\r\n    if (_cooldown>0) {\r\n      _cooldown--;\r\n      return 0.0f;\r\n    }\r\n    \/\/So we pulled the trigger. Put the weapon on cooldown.\r\n    _cooldown = 100 - (int)(50.0f * BonusPercent ());\r\n    _bullets--;\r\n    \/\/Roll to hit...\r\n    roll = RandomVal () % 100 + BASE_HIT_CHANCE;\r\n    roll += (int)_bonus;\r\n    if (roll < 100) \/\/ We missed.\r\n      return 0.0f;\r\n    \/\/Calculate damage.\r\n    damage = BASE_DAMAGE;\r\n    damage += (float)BASE_DAMAGE * BonusPercent ();\r\n    \/\/Roll for crit...\r\n    roll = RandomVal () % 100 + BASE_CRIT_CHANCE;\r\n    roll += (int)_bonus;\r\n    if (roll > 100) \/\/Yup, a crit.\r\n      damage += damage * (CRITICAL_DAMAGE + BonusPercent ());\r\n    return damage;    \r\n  }\r\n};\r\n\r\n\/*-----------------------------------------------------------------------------\r\nAnd now the program itself.\r\n-----------------------------------------------------------------------------*\/\r\n\r\nstatic bool over;\r\nstatic int  turn;\r\nstatic int  deaths_total;\r\nstatic int  deaths_player;\r\nstatic int  deaths_mook;\r\nstatic int  msg_count;\r\n\r\nint main (int argc, char* argv[])\r\n{\r\n  Fighter   player, mook;\r\n  \r\n  player.Spawn (5.0f);\r\n  mook.Spawn ();\r\n  RandomInit (2);\r\n  while (!over) {\r\n    \/\/Have these two idiots shoot each other at exactly the same time, so we \r\n    \/\/don't have to worry about who shot first. (It wasn't Greedo.)\r\n    mook.Hit (player.Shoot ());\r\n    player.Hit (mook.Shoot ());\r\n    \/\/Only print info occasionally, or the program takes forever.\r\n    if ((msg_count % 500) == 0)\r\n      printf (\"%5d %3d Player: %s | %3d Mook: %s\\n\", turn, deaths_mook, player.Bar (), deaths_player, mook.Bar ());\r\n    msg_count++;\r\n    \/\/See if either one died in the exchange.\r\n    if (player.Dead ()) {\r\n      player.Respawn ();\r\n      deaths_player++;\r\n      deaths_total++;\r\n    }\r\n    if (mook.Dead ()) {\r\n      mook.Respawn ();\r\n      deaths_mook++;\r\n      deaths_total++;\r\n    }\r\n    if (deaths_total >= 1000) \r\n      over = true;\r\n    turn++;\r\n  }\r\n  printf (\"Player kills: %d\\n\", deaths_mook);\r\n  printf (\"Mook kills: %d\\n\", deaths_player);\r\n  return 0;\r\n}\r\n<\/pre>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Borderlands games have this thing called &#8220;Badass ranks&#8221;. It&#8217;s this sort of meta-leveling system that&#8217;s not tied to any specific character, but is instead based on some global stateActually, in the first one it works a little different, but whatever. For now let&#8217;s focus on the Borderlands 2 and Pre-Sequel.. You complete goals, the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[66],"tags":[],"class_list":["post-24754","post","type-post","status-publish","format-standard","hentry","category-programming"],"_links":{"self":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/24754","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=24754"}],"version-history":[{"count":0,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=\/wp\/v2\/posts\/24754\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=24754"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=24754"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.shamusyoung.com\/twentysidedtale\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=24754"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}