Tomasz Wegrzanowski
Posted on April 28, 2022
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
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.)
becomesAC/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
Posted on April 28, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 30, 2022