MechWarrior Online Weapon Analysis

I played around with the weapons from MechWarrior Online in R. The following is created from an Rmarkdown document. You can also download the data set used here (weapons.txt).

Gathering the data

First we need to download the data from some website. There is one table which contains the columns of interest and seems reasonable to parse.

#url <- 'https://mwo.gamepedia.com/Weapons'
url <- 'http://mwo.smurfy-net.de/equipment'

weapons_orig <- htmltab(doc = url, which = "//th[text() = 'Damage']/ancestor::table")

The data needs to be cleaned up, though:

  • The column names somehow got wrong.
  • The weapon type and faction are not written in columns but above the tables. This is not tidy data yet, so it needs to be converted.
  • Some columns contain two variables, namely Range and Cooldown. The Range column sometimes contains a minimum range. For the gauss cannon, the cooldown contains a + which means the warmup time.
  • For better human readability the numbers are formatted with a , as a thousand separator. This needs to be removed.
  • Some columns contain - or n/a to mark NA values. These need to be replaced properly.
weapons <- weapons_orig

# We need to fix the column names.
colnames(weapons) <- c('Name', 'Damage', 'Heat', 'Cooldown', 'Range',
                       'Max_Range', 'Slots', 'Tons', 'Speed', 'Ammo/t', 'DPS',
                       'DPH', 'DPS/T', 'HPS', 'Impulse', 'Health', 'Cost')

# The row names also got wrong.
rownames(weapons) <- 1:nrow(weapons)

# Replace missing values.
weapons[weapons == '-'] <- NA
weapons[weapons == 'n/a'] <- NA

# Remove all dubious entries where there is not a proper name. These stem from
# the separators in the table.
weapons <- weapons[!is.na(weapons$Name), ]

# Add the type of weapons.
weapons$Type <- NA
weapons[1:36, ]$Type <- 'Ballistic'
weapons[37:65, ]$Type <- 'Energy'
weapons[66:110, ]$Type <- 'Missile'
weapons[111:118, ]$Type <- 'Support'

# Add the faction. This is easy because all clan weapons start with `C-`.
weapons$Faction <- 'Inner Sphere'
weapons$Faction[startsWith(weapons$Name, 'C-')] <- 'Clan'

# Separate columns which contain two variables.
weapons <- separate(weapons, 'Range', c('Min_Range', 'Optimal_Range'),
                    fill = 'left')
weapons <- separate(weapons, 'Cooldown', c('Cooldown', 'Warmup'),
                    sep = ' \\+ ', fill = 'right')

# Convert numeric columns into proper numerical values.
for (column in c('Damage', 'Heat', 'Cooldown', 'Warmup', 'Min_Range',
                 'Optimal_Range', 'Max_Range', 'Slots', 'Tons', 'Speed',
                 'Ammo/t', 'DPS', 'DPH', 'DPS/T', 'HPS', 'Impulse', 'Health',
                 'Cost')) {
    weapons[, column] <-
        as.numeric(sub(',', '', as.character(weapons[, column])))
}

# Type and faction are factors, make them so.
for (column in c('Type', 'Faction')) {
    weapons[, column] <- factor(weapons[, column])
}

# Weapons that do not generate any heat are currently marked as `NA`, but
# actually that is 0.
weapons$HPS[is.na(weapons$Heat)] <- 0.0
weapons$Heat[is.na(weapons$Heat)] <- 0.0
weapons$Cooldown[is.na(weapons$Cooldown)] <- 0.0
weapons$Warmup[is.na(weapons$Warmup)] <- 0.0
weapons$Min_Range[is.na(weapons$Min_Range)] <- 0.0

# Add a new column Period which is the total roundtrip time to fire a weapon.
weapons$Period <- weapons$Warmup + weapons$Cooldown

# For our analysis we only want offensive weapons, therefore we drop all the
# support weapons.
offensive <- filter(weapons, Type != 'Support')

# The missiles have more information packed in their names. There is a boolean
# that indicates the Artemis missile guidance system. And there is a number which
# gives the number of rockets fired. This number is not directly the damage. In
# the following these variables are split off into extra colums and the name is
# normalized.
missile <- offensive %>%
    filter(Type == 'Missile')
missile$Artemis <- endsWith(missile$Name, ' + ARTEMIS')
missile$Name <- sub(' \\+ ARTEMIS', '', missile$Name)
missile$Name <- sub('^C-', '', missile$Name)
missile$Size <- as.numeric(sub('[^0-9]+', '', missile$Name))
missile$Name <- sub(' \\d+$', '', missile$Name)

This is what the data now looks like:

knitr::kable(head(weapons))
Name Damage Heat Cooldown Warmup Min_Range Optimal_Range Max_Range Slots Tons Speed Ammo/t DPS DPH DPS/T HPS Impulse Health Cost Type Faction Period
AC/10 10 2.75 2.50 0.00 0 450 900 7 12 1100 20 4.00 3.64 0.33 1.10 0.060 15 400000 Ballistic Inner Sphere 2.50
AC/2 2 0.60 0.72 0.00 0 720 1440 1 6 2000 75 2.78 3.33 0.46 0.83 0.038 10 150000 Ballistic Inner Sphere 0.72
AC/20 20 6.00 4.00 0.00 0 270 540 10 14 650 7 5.00 3.33 0.36 1.50 0.130 25 600000 Ballistic Inner Sphere 4.00
AC/5 5 1.50 1.66 0.00 0 620 1240 4 8 1300 30 3.01 3.33 0.38 0.90 0.040 10 250000 Ballistic Inner Sphere 1.66
GAUSS RIFLE 15 1.00 5.00 0.75 0 660 1320 7 15 2000 10 2.61 15.00 0.17 0.17 0.050 10 600000 Ballistic Inner Sphere 5.75
HEAVY GAUSS RIFLE 25 2.00 5.00 0.00 0 180 900 11 18 1500 5 5.00 12.50 0.28 0.40 0.085 15 NA Ballistic Inner Sphere 5.00

Most column names should be self-explanatory. A few might not be this obvious:

Speed
Projectile speed
Ammo/t
Shots per ton in the ammunition crates
DPS
Damage per second
DPH
Damage per heat
DPS/T
Damage per second per ton
HPS
Heat per second

There are others that I don’t understand yet:

  • Impulse

Exploring the data

Now that the data is available in tidy format, we can do all sorts of things with it. Let us first have a look of damage vs. tons, grouped by weapon type and faction:

ggplot(offensive, aes(x = Tons, y = Damage)) +
    geom_point(aes(color = Slots)) +
    red_green_gradient +
    facet_grid(Faction ~ Type, labeller = label_both)
../../_images/unnamed-chunk-4-1.svg

This looks somewhat as expected. More damage generally means more tons to carry. The clans only have a limited number of ballistic weapons. It is interesting to see how they all lie on the same upward curve. There is no Inner Sphere weapon that can deal 20 damange with the same weight. But there are are Inner Sphere missile weapons that can deal lots of damage but weigh just a few tons. What are those?

ggplot(filter(offensive, Type == 'Missile', Faction == 'Inner Sphere'),
       aes(x = Tons, y = Damage)) +
    geom_point(aes(color = Slots)) +
    red_green_gradient +
    geom_label_repel(aes(label = Name), point.padding = 0.5,
                     arrow = arrow(length = unit(0.01, 'npc'))) +
    expand_limits(x = -3, y = -5) +
    expand_limits(x = 15)
../../_images/unnamed-chunk-5-1.svg

These are rocket launchers, a single shot weapon. Since they cannot be reloaded, it makes sense that they are so light.

We can have a look at the number of slots that the various weapon systems occupy:

ggplot(offensive, aes(Slots)) +
    geom_histogram(stat = 'count') +
    facet_grid(Faction ~ Type, labeller = label_both)
../../_images/unnamed-chunk-6-1.svg

However, this does not really say much because one can always put in multiple weapon systems of one type, and the mere availability of other systems does not limit one at all.

What about cooldown and heat? We see that there is some weak correlation between the two. It is rather uncorrelated to the damange dealt.

ggplot(offensive, aes(x = Cooldown, y = Heat)) +
    geom_point(aes(color = Damage)) +
    red_green_gradient +
    facet_grid(Faction ~ Type, labeller = label_both)
../../_images/unnamed-chunk-7-1.svg

Specific questions

Exploring the data was nice, but we want to find out specific

Brawling

For my Hunchback IIC I want to find some ballistic weapons such that it can more or less continiously fire. I want a high DPS and low HPS. Let’s see how the various types and factions are set up for this. The “Rocket Launcher” systems skew the plots because they are single-shot.

ggplot(filter(offensive, !startsWith(Name, 'ROCKET LAUNCHER')),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons)) +
    red_green_gradient +
    facet_grid(Faction ~ Type, labeller = label_both)
../../_images/unnamed-chunk-8-1.svg

We can see that energy weapons generally create much more heat than ballistic or missile weapons. This is no surprise because they do not need ammunition to compensate this. Since I am interested in clan ballistic weapons, I create a plot with just this selection.

ggplot(filter(offensive,
              Faction == 'Clan',
              Type == 'Ballistic'),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons, size = Slots)) +
    red_green_gradient +
    expand_limits(x = 0, y = 0) +
    geom_text_repel(aes(label = Name), point.padding = 1.5, box.padding = 0,
                    arrow = arrow(length = unit(0.01, 'npc'))) +
    labs(title = 'Clan Ballistic Weapons')
../../_images/unnamed-chunk-9-1.svg

Putting all weapons into a single plot with labels does not work, they overlap. Therefore we do a single facet for every weapon.

ggplot(filter(offensive,
              Faction == 'Clan',
              Type == 'Ballistic'),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons, size = Slots)) +
    red_green_gradient +
    facet_wrap(~ Name) +
    expand_limits(x = 0, y = 0) +
    labs(title = 'Clan Ballistic Weapons')
../../_images/unnamed-chunk-10-1.svg

Two C-Gauss Rifle seems like a good point, but that takes a bunch of slots and tons. The C-AC/20 has the same DPS, but the heat is a problem. I have seen this before. Also the range of the C-AC/20 is not that cool. Perhaps we should filter for weapons that have a slightly longer range because for a 64 km/h ‘Mech, close combat is not really the best idea. Let’s try 500 meter.

ggplot(filter(offensive,
              Faction == 'Clan',
              Type == 'Ballistic',
              Optimal_Range >= 500),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons, size = Slots)) +
    red_green_gradient +
    facet_wrap(~ Name) +
    expand_limits(x = 0, y = 0) +
    labs(title = 'Clan Ballistic Weapons')
../../_images/unnamed-chunk-11-1.svg

In said Hunchback IIC I can either fit two C-AC/10 or C-AC/5 with lots of ammunition or four C-AC/5 but not enough ammunition. One can see in this plot that just from the DPS-HPS perspective, the C-AC/10 is better than the C-AC/5. Although the cooldown is slightly longer, it can deal twice the damage. The HPS of the larger autocannon is even lower due to increased cooldown but only slightly increased heat.

Using the C-Ultra AC/10 intead of a C-AC/10 seems to make no sense. It generates more heat and has a chance of jamming. The advantages are less weight, less slots and less cost.

Assault aggrevator

I have one Jenner IIC with a lot of missile hardpoints. It came equipped with C-SRM. It works well against heavy ‘Mechs, but fails against other light ‘Mechs. Therefore I tried to give it C-Streak SRM, but it turns out that the missile lock takes too long to actually get a lock on some other light ‘Mech. Therefore I can never fire the rockets, making this useless. Now I went back to C-SRM and target this ‘Mech against heavy and assault class enemies. The remaining question is: Which missile system should I put into this ‘Mech?

Let’s have a look at the various clan missile systems that we got:

ggplot(filter(offensive,
              Faction == 'Clan',
              Type == 'Missile'),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons, size = Slots)) +
    red_green_gradient +
    facet_wrap(~ Name) +
    expand_limits(x = 0, y = 0) +
    labs(title = 'Clan Ballistic Weapons')
../../_images/unnamed-chunk-12-1.svg

This plot is not really helpful because the numbers are sorted lexigraphically and not by value. Also the Artemis missile guidance mixes things up. Therefore there is a data frame called missile that only contains the missile systems and these implicit variables are extracted as well. This allows us to re-group the weapon systems by name, size and Artemis (FALSE, TRUE):

ggplot(filter(missile,
              Faction == 'Clan',
              Name != 'STREAK SRM'),
       aes(x = HPS, y = DPS)) +
    geom_point(aes(color = Tons, size = Slots)) +
    red_green_gradient +
    facet_grid(Artemis ~ Name + Size) +
    expand_limits(x = 0, y = 0) +
    labs(title = 'Clan Ballistic Weapons')
../../_images/unnamed-chunk-13-1.svg

We see that there are no ATM with Artemis. For LRM and SRM, the Artemis system does not change DPS or HPS but only adds weight and slots. Since I want to aggrevate heavy and assault ‘Mechs, I can probably do without the Artemis system and pack more punch. Also I do not want the LRMs. So either SRM or ATM it is. The ATM has a really strange distance dependence. So just taking SRM is probably easier. Also it seems that with C-SRM 4 I can pack a similar DPS and HPS while using less weight and slots.

Long distance

My first ‘Mech is a Highlander IIC. Over time the loadout seems to have gravitated towards long distance, and I like to keep it this way. The mobility of that 90 ton thing is like a siege tower, so want to play it like one. It should have a lot of C-LRM + Artemis and some other line-of-sight weapons. There are hardpoints for all three weapon categories, so I can pretty much put a little of everything into it.

The following table has the weapons with a range of more than 600 meter, sorted by optimal range, then maximal range and then damage.

long_range <- offensive %>%
    filter(Faction == 'Clan', Optimal_Range >= 600) %>%
    arrange(desc(Optimal_Range), desc(Max_Range), Damage)
knitr::kable(long_range[c(1:4, 7:8, 11, 13, 16)])
Name Damage Heat Cooldown Optimal_Range Max_Range Speed DPS HPS
C-AC/2 2 0.60 0.72 900 1800 2000 2.78 0.83
C-LB2-X AC 2 0.60 0.72 900 1800 2000 2.78 0.83
C-LRM 5 + ARTEMIS 5 2.40 3.50 900 900 160 1.43 0.69
C-LRM 5 5 2.40 3.50 900 900 160 1.43 0.69
C-LRM 10 + ARTEMIS 10 4.00 4.00 900 900 160 2.50 1.00
C-LRM 10 10 4.00 4.00 900 900 160 2.50 1.00
C-LRM 15 + ARTEMIS 15 5.00 4.30 900 900 160 3.49 1.16
C-LRM 15 15 5.00 4.30 900 900 160 3.49 1.16
C-LRM 20 + ARTEMIS 20 6.00 4.60 900 900 160 4.35 1.30
C-LRM 20 20 6.00 4.60 900 900 160 4.35 1.30
C-ULTRA AC/2 2 0.80 0.72 810 1620 2000 2.78 1.11
C-ER PPC 15 14.50 4.50 810 1620 1500 3.33 3.22
C-ER LRG LASER 11 10.00 3.75 740 1480 NA 2.16 1.96
C-AC/5 5 1.50 1.66 720 1440 1300 3.01 0.90
C-LB5-X AC 5 1.00 1.66 720 1440 1330 3.01 0.60
C-GAUSS RIFLE 15 1.00 5.00 660 1320 2000 2.61 0.17
C-ULTRA AC/5 5 1.66 1.66 630 1260 1300 3.01 1.00
C-LRG PULSE LASER 12 10.00 3.20 600 840 NA 2.80 2.33

We can see that the best range is given by the C-AC/2, though the damage is going to pretty low. The C-Gauss Rifle will me much better, but has this warmup time. Regarding the projectile speed, they are on par. What I would not have expected is that the C-AC/2 has a better DPS than the C-Gauss Rifle.