DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

4 2

Open Source Adventures: Episode 46: BATTLETECH Damage Per Ton Weapon Ranking

Let's start writing some BATTLETECH analysis code. By the way the game is capitalized like that, but in the code I'll write it as BattleTech as it looks a bit dumb.

Analysis Script

The script is very simple. You point it at game root directory. Then it reads some JSONs, and prints a report.

#!/usr/bin/env ruby

require "json"
require "memoist"
require "pathname"
require "pry"

class Weapon
  extend Memoist
  attr_reader :data

  def initialize(path)
    @data = JSON.parse(path.read)
  end

  memoize def name
    bonuses = [bonus_a, bonus_b].compact
    if bonuses.empty?
      base_name
    else
      "#{base_name} (#{bonuses.join(", ")})"
    end
  end

  memoize def base_name
    [
      data["Description"]["Name"],
      data["Description"]["UIName"],
    ].compact.last.gsub(" +", "+")
  end

  memoize def bonus_a
    data["BonusValueA"] == "" ? nil : data["BonusValueA"].gsub(/[a-z]\K\./, "")
  end

  memoize def bonus_b
    data["BonusValueB"] == "" ? nil : data["BonusValueB"].gsub(/[a-z]\K\./, "")
  end

  def category
    @data["Category"]
  end

  def tonnage
    @data["Tonnage"]
  end

  def damage
    shots * base_damage
  end

  def base_damage
    @data["Damage"]
  end

  def shots
    @data["ShotsWhenFired"]
  end

  def heat
    @data["HeatGenerated"]
  end

  def ammo_category
    @data["ammoCategoryID"]
  end

  def purchasable?
    @data["Description"]["Purchasable"]
  end
end

class BattleTechGame
  extend Memoist

  def initialize(game_root)
    @game_root = Pathname(game_root)
  end

  memoize def data_root
    @game_root + "BattleTech_Data/StreamingAssets/data"
  end

  memoize def weapon_files
    data_root
      .glob("weapon/*.json")
      .select{|n| n.basename.to_s != "WeaponTemplate.json"}
  end

  memoize def weapons
    weapon_files.map{|path| Weapon.new(path)}
  end

  def print_weapons_report
    weapons
      .select{|w| w.category != "Melee" and w.name != "AI Laser"}
      .sort_by{|w|
        [w.damage.to_f / w.tonnage, w.damage, w.name]
      }
      .reverse
      .each{|w|
        puts "* #{w.name}: #{w.damage} / #{w.tonnage}"
      }
  end
end

game = BattleTechGame.new(ARGV[0])

game.print_weapons_report
Enter fullscreen mode Exit fullscreen mode

Some points:

  • memoize used aggressively, mostly because why not
  • we want to exclude some weapons (Melee, DFA, and AI Laser) from analysis as they're not real weapons you can buy and use
  • we need some stylistic changes for nicer names, so AC/10 + + + (+ 50% Crit., + 20 Stb.Dmg.) becomes AC/10+++ (+ 50% Crit, + 20 StbDmg)

Report

Here's the most naive report. This ranking assumes ammo is free, and heat is free, ignores weapon range, any special effects, and so on:

  • MG++ (+ 5 Shots, - 0.5 Ton): 30 / 0.0
  • MG++ (+ 50% Crit, - 0.5 Ton): 15 / 0.0
  • ER S Laser++ (+ 10 Dmg): 40 / 0.5
  • ER S Laser+ (+ 5 Dmg): 35 / 0.5
  • S Laser+++ (+ 10 Dmg, + 50% Crit): 30 / 0.5
  • S Laser++ (+ 10 Dmg): 30 / 0.5
  • MG+ (+ 5 Shots): 30 / 0.5
  • ER S Laser: 30 / 0.5
  • S Laser++ (+ 5 Dmg, + 25% Crit): 25 / 0.5
  • S Laser+ (+ 5 Dmg): 25 / 0.5
  • S Pulse++ (+ 10 Dmg, - 2 Heat): 45 / 1
  • ER M Laser++ (+ 10 Dmg): 45 / 1
  • S Pulse+ (+ 5 Dmg, - 1 Heat): 40 / 1
  • ER M Laser+ (+ 5 Dmg): 40 / 1
  • S Laser++ (+ 50% Crit): 20 / 0.5
  • S Laser++ (+ 3 Acc): 20 / 0.5
  • S Laser+ (+ 25% Crit): 20 / 0.5
  • S Laser+ (+ 1 Acc): 20 / 0.5
  • S Laser: 20 / 0.5
  • S Pulse: 35 / 1
  • M Laser++ (+ 10 Dmg): 35 / 1
  • ER M Laser: 35 / 1
  • M Laser+ (+ 5 Dmg): 30 / 1
  • MG+ (+ 25% Crit): 15 / 0.5
  • MG: 15 / 0.5
  • M Pulse++ (- 4 Heat, + 1 Acc): 50 / 2
  • M Laser+++ (+ 3 Acc, + 50% Crit): 25 / 1
  • M Laser++ (+3 Acc): 25 / 1
  • M Laser++ (+ 50% Crit): 25 / 1
  • M Laser++ (+ 1 Acc, + 25% Crit): 25 / 1
  • M Laser+ (+1 Acc): 25 / 1
  • M Laser+ (+ 25% Crit): 25 / 1
  • M Laser: 25 / 1
  • SRM6+++ (+ 4 Dmg, + 2 StbDmg): 72 / 3
  • SRM6++ (+ 4 Dmg): 72 / 3
  • SRM4+++ (+ 4 Dmg, + 2 StbDmg): 48 / 2
  • SRM4++ (+ 4 Dmg): 48 / 2
  • SRM2+++ (+ 4 Dmg, + 2 StbDmg): 24 / 1
  • SRM2++ (+ 4 Dmg): 24 / 1
  • M Pulse+ (- 2 Heat): 45 / 2
  • SRM6++ (+ 2 Dmg, + 1 StbDmg): 60 / 3
  • SRM6+ (+ 2 Dmg): 60 / 3
  • SRM4++ (+ 2 Dmg, + 1 StbDmg): 40 / 2
  • SRM4+ (+ 2 Dmg): 40 / 2
  • M Pulse: 40 / 2
  • SRM2++ (+ 2 Dmg, + 1 StbDmg): 20 / 1
  • SRM2+ (+ 2 Dmg): 20 / 1
  • SRM6++ (+ 50% Crit): 48 / 3
  • SRM6+ (+ 25% Crit): 48 / 3
  • SRM6: 48 / 3
  • SRM4++ (+ 50% Crit): 32 / 2
  • SRM4+ (+ 25% Crit): 32 / 2
  • SRM4: 32 / 2
  • SRM2++ (+ 50% Crit): 16 / 1
  • SRM2+ (+ 25% Crit): 16 / 1
  • SRM2: 16 / 1
  • LRM5++ (+ 2 Dmg): 30 / 2
  • LRM15++ (+ 2 Dmg): 90 / 7
  • ER L Laser++ (+ 5 Dmg, - 1 Ton): 50 / 4
  • LRM5+ (+ 1 Dmg): 25 / 2
  • LRM20++ (+ 2 Dmg): 120 / 10
  • LRM10++ (+ 2 Dmg): 60 / 5
  • ER PPC++ (+ 10 Dmg, - 1 Ton): 70 / 6
  • L Pulse++ (+ 10 Dmg, - 1 Ton): 65 / 6
  • ER PPC+ (+ 5 Dmg, - 1 Ton): 65 / 6
  • LRM15+ (+ 1 Dmg): 75 / 7
  • LRM20+ (+ 1 Dmg): 100 / 10
  • L Pulse+ (+ 5 Dmg, - 1 Ton): 60 / 6
  • LRM10+ (+ 1 Dmg): 50 / 5
  • L Laser+++ (+ 10 Dmg, + 3 Acc): 50 / 5
  • L Laser++ (+ 10 Dmg): 50 / 5
  • ER L Laser+ (+ 5 Dmg): 50 / 5
  • LRM5+++ (+ 50% Crit, + 2 StbDmg): 20 / 2
  • LRM5++ (+ 50% Crit): 20 / 2
  • LRM5++ (+ 25% Crit, + 1 StbDmg): 20 / 2
  • LRM5++ (+ 2 StbDmg): 20 / 2
  • LRM5+ (+ 25% Crit): 20 / 2
  • LRM5+ (+ 1 StbDmg): 20 / 2
  • LRM5: 20 / 2
  • Flamer+ (* 4 Ammo, + 5 Dmg): 10 / 1
  • L Laser++ (+ 5 Dmg, + 1 Acc): 45 / 5
  • L Laser+ (+ 5 Dmg): 45 / 5
  • ER L Laser: 45 / 5
  • AC/20+++ (+ 20 Dmg, + 20 StbDmg): 120 / 14
  • AC/20++ (+ 20 Dmg): 120 / 14
  • PPC++ (+ 10 Dmg): 60 / 7
  • LRM15+++ (+ 50% Crit, + 2 StbDmg): 60 / 7
  • LRM15++ (+ 50% Crit): 60 / 7
  • LRM15++ (+ 25% Crit, + 1 StbDmg): 60 / 7
  • LRM15++ (+ 2 StbDmg): 60 / 7
  • LRM15+ (+ 25% Crit): 60 / 7
  • LRM15+ (+ 1 StbDmg): 60 / 7
  • LRM15: 60 / 7
  • ER PPC: 60 / 7
  • LRM20+++ (+ 50% Crit, + 2 StbDmg): 80 / 10
  • LRM20++ (+ 50% Crit): 80 / 10
  • LRM20++ (+ 25% Crit, + 1 StbDmg): 80 / 10
  • LRM20++ (+ 2 StbDmg): 80 / 10
  • LRM20+ (+ 25% Crit): 80 / 10
  • LRM20+ (+ 1 StbDmg): 80 / 10
  • LRM20: 80 / 10
  • LRM10+++ (+ 50% Crit, + 2 StbDmg): 40 / 5
  • LRM10++ (+ 50% Crit): 40 / 5
  • LRM10++ (+ 25% Crit, + 1 StbDmg): 40 / 5
  • LRM10++ (+ 2 StbDmg): 40 / 5
  • LRM10+ (+ 25% Crit): 40 / 5
  • LRM10+ (+ 1 StbDmg): 40 / 5
  • LRM10: 40 / 5
  • L Laser++ (+ 50% Crit): 40 / 5
  • L Laser++ (+ 3 Acc): 40 / 5
  • L Laser+ (+ 25% Crit): 40 / 5
  • L Laser+ (+ 1 Acc): 40 / 5
  • L Laser: 40 / 5
  • AC/20++ (+ 10 Dmg, + 10 StbDmg): 110 / 14
  • AC/20+ (+ 10 Dmg): 110 / 14
  • PPC+ (+ 5 Dmg): 55 / 7
  • L Pulse: 55 / 7
  • AC/20+++ : 100 / 14
  • AC/20++ (+ 50% Crit): 100 / 14
  • AC/20++ (+ 4 Acc): 100 / 14
  • AC/20++ (+ 20 StbDmg): 100 / 14
  • AC/20+ (+ 25% Crit): 100 / 14
  • AC/20+ (+ 2 Acc): 100 / 14
  • AC/20+ (+ 10 StbDmg): 100 / 14
  • AC/20: 100 / 14
  • PPC++ (+ 4 Acc): 50 / 7
  • PPC++ (+ 30 StbDmg): 50 / 7
  • PPC+ (+ 2 Acc): 50 / 7
  • PPC+ (+ 15 StbDmg): 50 / 7
  • PPC: 50 / 7
  • AC/5++ (+ 10 Dmg): 55 / 8
  • AC/5+ (+ 5 Dmg): 50 / 8
  • AC/10++ (+ 10 Dmg): 70 / 12
  • AC/2++ (+ 10 Dmg): 35 / 6
  • Gauss Rifle++ (- 2 Tons, - 1 Slot): 75 / 13
  • Gauss Rifle+ (- 2 Tons): 75 / 13
  • AC/5+++ (+ 4 Acc, + 20 StbDmg): 45 / 8
  • AC/5++ (+ 50% Crit): 45 / 8
  • AC/5++ (+ 4 Acc): 45 / 8
  • AC/5++ (+ 20 StbDmg): 45 / 8
  • AC/5++ (+ 2 Acc, + 10 StbDmg): 45 / 8
  • AC/5+ (+ 25% Crit): 45 / 8
  • AC/5+ (+ 2 Acc): 45 / 8
  • AC/5+ (+ 10 StbDmg): 45 / 8
  • AC/5: 45 / 8
  • AC/10+ (+ 5 Dmg): 65 / 12
  • Gauss Rifle: 75 / 15
  • AC/10+++ (+ 50% Crit, + 20 StbDmg): 60 / 12
  • AC/10++ (+ 50% Crit): 60 / 12
  • AC/10++ (+ 4 Acc): 60 / 12
  • AC/10++ (+ 25% Crit, + 10 StbDmg): 60 / 12
  • AC/10++ (+ 20 StbDmg): 60 / 12
  • AC/10+ (+ 25% Crit): 60 / 12
  • AC/10+ (+ 2 Acc): 60 / 12
  • AC/10+ (+ 10 StbDmg): 60 / 12
  • AC/10: 60 / 12
  • AC/2+ (+ 5 Dmg): 30 / 6
  • Flamer+++ (* 40 Ammo): 5 / 1
  • Flamer++ (* 4 Ammo, + 5 Dmg (H)): 5 / 1
  • Flamer (* 4 Ammo): 5 / 1
  • AC/2+++ (+ 4 Acc, + 50% Crit): 25 / 6
  • AC/2++ (+ 50% Crit): 25 / 6
  • AC/2++ (+ 4 Acc): 25 / 6
  • AC/2++ (+ 20 StbDmg): 25 / 6
  • AC/2++ (+ 2 Acc, + 25% Crit): 25 / 6
  • AC/2+ (+ 25% Crit): 25 / 6
  • AC/2+ (+ 2 Acc): 25 / 6
  • AC/2+ (+ 10 StbDmg): 25 / 6
  • AC/2: 25 / 6

The winners are some weapons that have weight of 0. But that's only true if we ignore their ammo. Ignoring that we have enormous range from 80:1 (ER S Laser++) to 4.16:1 (for AC/2).

Unfortunately we can't really use these amazing weapons that consume no ammo and generate no heat. In the next episode we'll try to include heat and ammo in the analysis to get a more useful ranking.

Story so far

All the code is on GitHub.

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (1)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay