Advertisement
Rivx

Devouring Arrow simulation

Jun 10th, 2015
247
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 9.21 KB | None | 0 0
  1. #include <stdio.h>
  2. #include <math.h>
  3. #include <stdlib.h>
  4. #include <iostream>
  5. #include <vector>
  6. #include <algorithm>
  7. #include <random>
  8. using namespace std;
  9.  
  10. const double EPS = 1e-6;
  11. const double INF = 1e+12;
  12.  
  13. // CHARACTER CONFIG
  14. const double BonusPierceChance = 0.25; // Ninth Cirri Satchel
  15. const int GuaranteedPierce = 2; // Buriza-do Kyanon
  16. const double CritChance = 0.6; // CC for Spray of Teeth
  17. const int Trials = 1000000; // number of simulations
  18.  
  19. // TARGET CONFIG
  20. struct Config {
  21.   double distance;
  22.   double radius;
  23.   double size;
  24.   int count;
  25. } config = {
  26.   22, // distance from the target
  27.   1, // target spread (max distance from the center)
  28.   2.5, // target size (collision radius)
  29.   1, // number of targets
  30. };
  31.  
  32. // SKILL DATA (do not change)
  33. const double BaseDamage = 1.55;
  34. const double Speed = 1.3;
  35. const double SeekInterval = 0.3;
  36. const double SeekIntervalDelta = 0.3;
  37. const double SearchInitial = 11;
  38. const double SearchAfterPierce = 20;
  39. const double PierceChance = 0.35;
  40. const double MaxTravelDistance = 120;
  41. const double MaxDistanceFromCast = 90;
  42. const double B_ConeSize = 50;
  43. const double C_BonusDamage = 0.7;
  44. const double E_Damage = 0.6;
  45. const double E_Range = 10;
  46. const double D_PierceChanceBonus = 0.15;
  47.  
  48. // SKILL RUNES
  49. struct {
  50.   char sym;
  51.   char const* name;
  52. } Runes[] = {
  53.   { 'x', "Unruned" },
  54.   { 'd', "Puncturing Arrow" },
  55.   { 'a', "Serrated Arrow" },
  56.   { 'b', "Shatter Shot" },
  57.   { 'c', "Devouring Arrow" },
  58.   { 'e', "Spray of Teeth" },
  59. };
  60.  
  61. // vector class
  62. struct Vector {
  63.   double x, y;
  64.   Vector(double x = 0, double y = 0)
  65.     : x(x), y(y)
  66.   {}
  67.   Vector(Vector const& v)
  68.     : x(v.x), y(v.y)
  69.   {}
  70.  
  71.   double len() const {
  72.     return sqrt(x * x + y * y);
  73.   }
  74.   double norm() const {
  75.     return sqrt(x * x + y * y);
  76.   }
  77.   double len2() const {
  78.     return x * x + y * y;
  79.   }
  80.   double operator ~() const {
  81.     return sqrt(x * x + y * y);
  82.   }
  83.  
  84.   Vector& normalize() {
  85.     double n = norm();
  86.     x /= n;
  87.     y /= n;
  88.     return *this;
  89.   }
  90.   Vector normalized() const {
  91.     double n = norm();
  92.     return Vector(x / n, y / n);
  93.   }
  94.  
  95.   Vector operator -() const {
  96.     return Vector(-x, -y);
  97.   }
  98.   Vector operator +() const {
  99.     return Vector(x, y);
  100.   }
  101.   Vector perp() const {
  102.     return Vector(-y, x);
  103.   }
  104.  
  105.   Vector& operator =(Vector const& rhs) {
  106.     x = rhs.x;
  107.     y = rhs.y;
  108.     return *this;
  109.   }
  110.   Vector& operator +=(Vector const& rhs) {
  111.     x += rhs.x;
  112.     y += rhs.y;
  113.     return *this;
  114.   }
  115.   Vector& operator -=(Vector const& rhs) {
  116.     x -= rhs.x;
  117.     y -= rhs.y;
  118.     return *this;
  119.   }
  120.   Vector& operator *=(double rhs) {
  121.     x *= rhs;
  122.     y *= rhs;
  123.     return *this;
  124.   }
  125.   Vector& operator /=(double rhs) {
  126.     x /= rhs;
  127.     y /= rhs;
  128.     return *this;
  129.   }
  130.  
  131.   friend Vector operator +(Vector const& lhs, Vector const& rhs) {
  132.     return Vector(lhs.x + rhs.x, lhs.y + rhs.y);
  133.   }
  134.   friend Vector operator -(Vector const& lhs, Vector const& rhs) {
  135.     return Vector(lhs.x - rhs.x, lhs.y - rhs.y);
  136.   }
  137.   friend Vector operator *(Vector const& lhs, double rhs) {
  138.     return Vector(lhs.x * rhs, lhs.y * rhs);
  139.   }
  140.   friend Vector operator *(double lhs, Vector const& rhs) {
  141.     return Vector(lhs * rhs.x, lhs * rhs.y);
  142.   }
  143.   friend Vector operator /(Vector const& lhs, double rhs) {
  144.     return Vector(lhs.x / rhs, lhs.y / rhs);
  145.   }
  146.   friend double operator &(Vector const& lhs, Vector const& rhs) {
  147.     return lhs.x * rhs.x + lhs.y * rhs.y;
  148.   }
  149.   friend double operator ^(Vector const& lhs, Vector const& rhs) {
  150.     return lhs.x * rhs.y - lhs.y * rhs.x;
  151.   }
  152. };
  153.  
  154. mt19937_64 engine;
  155. uniform_real_distribution<double> rng(0, 1);
  156. uniform_real_distribution<double> seekRng(SeekInterval, SeekInterval + SeekIntervalDelta);
  157.  
  158. const double PI = 2.0 * acos(0);
  159. // rotate a vector `deg' degrees CCW
  160. Vector rotate(Vector& v, double deg) {
  161.   deg *= PI / 180.0;
  162.   double cs = cos(deg);
  163.   double sn = sin(deg);
  164.   return Vector(v.x * cs - v.y * sn, v.x * sn + v.y * cs);
  165. }
  166.  
  167. // target setup
  168. struct Setup {
  169.   Vector pos, dir; // current projectile position/direction
  170.   vector<Vector> targets; // target positions
  171.   double size; // target sizes
  172.  
  173.   // generate random targets based on the config
  174.   Setup(Config const& cfg) {
  175.     uniform_real_distribution<double> distr(-cfg.radius, cfg.radius);
  176.     for (int i = 0; i < cfg.count; ++i) {
  177.       Vector cur;
  178.       do {
  179.         cur.x = distr(engine);
  180.         cur.y = distr(engine);
  181.       } while (cur.len2() > cfg.radius * cfg.radius);
  182.       targets.push_back(cur);
  183.     }
  184.     pos = Vector(-cfg.distance, 0);
  185.     dir = Vector(1, 0);
  186.     size = cfg.size;
  187.   }
  188.  
  189.   // find the closest target to the current position
  190.   Vector const& closest() {
  191.     size_t index = 0;
  192.     double dist2 = (targets[0] - pos).len2();
  193.     for (size_t i = 1; i < targets.size(); ++i) {
  194.       double cur2 = (targets[i] - pos).len2();
  195.       if (cur2 < dist2) {
  196.         dist2 = cur2;
  197.         index = i;
  198.       }
  199.     }
  200.     return targets[index];
  201.   }
  202.  
  203.   // get the distance until collision with the given target,
  204.   // or INF if the ray does not intersect the target
  205.   double impact(Vector const& target) {
  206.     Vector to = target - pos;
  207.     double dy = abs(dir ^ to);
  208.     if (dy > size - EPS) return INF;
  209.     double dx = dir & to;
  210.     double dt = sqrt(size * size - dy * dy);
  211.     if (dx - dt < EPS) return INF;
  212.     return dx - dt;
  213.   }
  214.   // get the time until next collision, or INF if no collisions
  215.   double impact() {
  216.     double best = impact(targets[0]);
  217.     for (size_t i = 1; i < targets.size(); ++i) {
  218.       best = min(best, impact(targets[i]));
  219.     }
  220.     return (best < INF ? best / Speed : best);
  221.   }
  222.   // get the number of targets within `range' yards of current position
  223.   int aoe(double range) const {
  224.     int count = 0;
  225.     range += size;
  226.     for (auto& v : targets) {
  227.       if ((v - pos).len2() < range * range) {
  228.         ++count;
  229.       }
  230.     }
  231.     return count;
  232.   }
  233. };
  234.  
  235. double run(Setup& cfg, char rune) {
  236.   double impact = cfg.impact(); // time of next collision
  237.   double finish = MaxTravelDistance / Speed; // projectile expiration time
  238.   double seek = seekRng(engine) * 60; // time until next direction change
  239.   double prev = 0; // previous event time
  240.   double seekRange = SearchInitial; // current seek range (11 before first hit, 20 after)
  241.   double total = 0; // total damage
  242.   double damage = BaseDamage; // current damage
  243.   double chance = PierceChance + (rune == 'd' ? D_PierceChanceBonus : 0) + BonusPierceChance; // pierce chance
  244.   int index = 0; // number of pierces
  245.   while (finish > impact || finish > seek) {
  246.     if (impact < seek) {
  247.       // next event = collision
  248.       total += damage; // add current damage
  249.       seekRange = SearchAfterPierce; // now seek range = 20
  250.       cfg.pos += cfg.dir * Speed * (impact - prev); // update projectile position
  251.       prev = impact; // update last event time
  252.       if (rune == 'e') {
  253.         // add Spray of Teeth damage (TODO: currently assumes it can crit as well)
  254.         total += E_Damage * CritChance * cfg.aoe(E_Range);
  255.       }
  256.       // check if we should pierce (Buriza gives 1-2 guaranteed pierces; otherwise do rng check)
  257.       if (!(rune && index < GuaranteedPierce) && rng(engine) > chance) break;
  258.       ++index;
  259.       if (rune == 'b' && index == 1) {
  260.         // fire spray of teeth on the first pierce
  261.         // (assuming the extra projectiles are unruned and do not benefit from Buriza)
  262.         Setup left(cfg);
  263.         left.dir = rotate(cfg.dir, B_ConeSize);
  264.         total += run(left, 0);
  265.         Setup right(cfg);
  266.         right.dir = rotate(cfg.dir, -B_ConeSize);
  267.         total += run(right, 0);
  268.       }
  269.       if (rune == 'c') {
  270.         // apply Devouring Arrow damage
  271.         damage += C_BonusDamage * BaseDamage;
  272.       }
  273.       impact = prev + cfg.impact(); // calculate next collision time
  274.     } else {
  275.       // next event = seek
  276.       cfg.pos += cfg.dir * Speed * (seek - prev); // update projectile position
  277.       prev = seek; // update last event time
  278.       auto to = cfg.closest(); // find closest target
  279.       if ((cfg.pos - to).len2() < seekRange * seekRange) {
  280.         cfg.dir = (to - cfg.pos).normalized(); // change direction
  281.         impact = prev + cfg.impact(); // calculate next collision time
  282.       }
  283.       seek += seekRng(engine) * 60; // get next redirect time
  284.     }
  285.   }
  286.   return total;
  287. }
  288.  
  289. // average result over a bunch of tests
  290. double run_avg(Config const& cfg, char rune) {
  291.   double avg = 0;
  292.   for (int i = 0; i < Trials; ++i) {
  293.     avg += run(Setup(cfg), rune);
  294.   }
  295.   return avg / Trials;
  296. }
  297.  
  298. // find the best distance to shoot from
  299. pair<double, double> run_best(Config& cfg, char rune, double from, double to, double delta) {
  300.   cfg.distance = from;
  301.   pair<double, double> best(cfg.distance, run_avg(cfg, rune));
  302.   for (cfg.distance = from + delta; cfg.distance <= to; cfg.distance += delta) {
  303.     pair<double, double> cur(cfg.distance, run_avg(cfg, rune));
  304.     if (cur.second > best.second) {
  305.       best = cur;
  306.     }
  307.   }
  308.   return best;
  309. }
  310.  
  311. template<class X, class Y>
  312. ostream& operator <<(ostream& os, pair<X, Y> const& p) {
  313.   return os << p.first << " " << p.second;
  314. }
  315.  
  316. int main() {
  317.   for (auto& rune : Runes) {
  318.     cout << rune.name << ": " << run_avg(config, rune.sym) << endl;
  319.   }
  320.   return 0;
  321. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement