Open Source Adventures: Episode 45: How BATTLETECH Game Stores Data
Tags: gamedev, battletech
Published: 2022-04-28 19:09:59

Recently I've been playing a lot of [BATTLETECH](https://store.steampowered.com/app/637090/BATTLETECH/).

Most similar games like let's say XCOM2 or Civ5 have fairly limited amount of character classes, weapons, so it's very easy to learn the difference between a Muton and a Sectoid, or a rocket launcher and a flamethrower, or between a Pikeman and a Catapult (special units in Civ5 are generally just base units with some specific bonuses).

Due to its origins as figurine game, BATTLETECH has far too many different equipment, with trivial differences.

There's 103 mechs, 172 weapons, and a lot of other equipment. It's really difficult to figure out which builds are good and which are not.

I've seen some Google Spreadsheets with mechs and weapons, but they're really satisfactory, and often only cover base weapons, not the whole set.

### Where BATTLETECH game stores data

If you have the Steam version, by default the game will be installed to `C:\Program Files (x86)\Steam\steamapps\common\BATTLETECH`. I'm not sure

Inside that folder, you need to dig in further into `BattleTech_Data\StreamingAssets\data`.

Historically most games coded their data in custom formats, so the first part of any such project would be writing some custom parsers. Most of these formats were fairly simple, but occasionally you'd run into some crazy complex data format - or even worse game data hardcoded into code.

But we live in the future, and the data is largely JSON.

### Data of interest

The data that interest me the most are two folders - `weapon` and `mech`/`chassis`.

There's a few extra folders that contain a bit of extra data like `ammunition`, `ammunitionBox`, and maybe `heatsinks`, `upgrades` etc. Fortunately all of it is JSON.

Some of the other data (like loot tables) are in `.csv`. There's some binary data in there too, like maps and audio assets, but pretty much everything we want is JSON.

### Example Mech JSON

Here's an example of `chassis/chassisdef_kintaro_KTO-18.json` (automatically reformatted for readability):

```json
{
  "Description": {
    "Cost": 5200000,
    "Rarity": 3,
    "Purchasable": true,
    "Manufacturer": "",
    "Model": "",
    "UIName": "Kintaro",
    "Id": "chassisdef_kintaro_KTO-18",
    "Name": "Kintaro",
    "Details": "The Kintaro 18 is… a beast of a machine. It's fast for its size, mounts heavy armor, and can literally shred most other 'Mechs with its almost ridiculous amount of close-range weaponry. But, it runs hotter than Hell's saunas.",
    "Icon": "uixTxrIcon_kintaro"
  },
  "MovementCapDefID": "movedef_kintaro_KTO-18",
  "PathingCapDefID": "pathingdef_medium",
  "HardpointDataDefID": "hardpointdatadef_kintaro",
  "PrefabIdentifier": "chrPrfMech_kintaroBase-001",
  "PrefabBase": "kintaro",
  "Tonnage": 55,
  "InitialTonnage": 27,
  "weightClass": "MEDIUM",
  "BattleValue": 5148000,
  "Heatsinks": 0,
  "TopSpeed": 80,
  "TurnRadius": 90,
  "MaxJumpjets": 5,
  "Stability": 130,
  "StabilityDefenses": [
    0,
    0,
    0,
    0,
    0,
    0
  ],
  "SpotterDistanceMultiplier": 1,
  "VisibilityMultiplier": 1,
  "SensorRangeMultiplier": 1,
  "Signature": 0,
  "Radius": 5,
  "PunchesWithLeftArm": false,
  "MeleeDamage": 70,
  "MeleeInstability": 55,
  "MeleeToHitModifier": 0,
  "DFADamage": 70,
  "DFAToHitModifier": 0,
  "DFASelfDamage": 70,
  "DFAInstability": 70,
  "Locations": [
    {
      "Location": "Head",
      "Hardpoints": [],
      "Tonnage": 0,
      "InventorySlots": 1,
      "MaxArmor": 45,
      "MaxRearArmor": -1,
      "InternalStructure": 16
    },
    {
      "Location": "LeftArm",
      "Hardpoints": [
        {
          "WeaponMount": "Missile",
          "Omni": false
        },
        {
          "WeaponMount": "Missile",
          "Omni": false
        },
        {
          "WeaponMount": "Energy",
          "Omni": false
        }
      ],
      "Tonnage": 0,
      "InventorySlots": 8,
      "MaxArmor": 90,
      "MaxRearArmor": -1,
      "InternalStructure": 45
    },
    {
      "Location": "LeftTorso",
      "Hardpoints": [],
      "Tonnage": 0,
      "InventorySlots": 10,
      "MaxArmor": 130,
      "MaxRearArmor": 65,
      "InternalStructure": 65
    },
    {
      "Location": "CenterTorso",
      "Hardpoints": [
        {
          "WeaponMount": "Missile",
          "Omni": false
        }
      ],
      "Tonnage": 0,
      "InventorySlots": 4,
      "MaxArmor": 180,
      "MaxRearArmor": 90,
      "InternalStructure": 90
    },
    {
      "Location": "RightTorso",
      "Hardpoints": [
        {
          "WeaponMount": "Missile",
          "Omni": false
        },
        {
          "WeaponMount": "Missile",
          "Omni": false
        }
      ],
      "Tonnage": 0,
      "InventorySlots": 10,
      "MaxArmor": 130,
      "MaxRearArmor": 65,
      "InternalStructure": 65
    },
    {
      "Location": "RightArm",
      "Hardpoints": [
        {
          "WeaponMount": "Energy",
          "Omni": false
        }
      ],
      "Tonnage": 0,
      "InventorySlots": 8,
      "MaxArmor": 90,
      "MaxRearArmor": -1,
      "InternalStructure": 45
    },
    {
      "Location": "LeftLeg",
      "Hardpoints": [],
      "Tonnage": 0,
      "InventorySlots": 4,
      "MaxArmor": 130,
      "MaxRearArmor": -1,
      "InternalStructure": 65
    },
    {
      "Location": "RightLeg",
      "Hardpoints": [],
      "Tonnage": 0,
      "InventorySlots": 4,
      "MaxArmor": 130,
      "MaxRearArmor": -1,
      "InternalStructure": 65
    }
  ],
  "LOSSourcePositions": [
    {
      "x": 0,
      "y": 15,
      "z": 0
    },
    {
      "x": 3,
      "y": 15,
      "z": 0
    },
    {
      "x": -3,
      "y": 15,
      "z": 0
    }
  ],
  "LOSTargetPositions": [
    {
      "x": 0,
      "y": 15,
      "z": 0
    },
    {
      "x": 3,
      "y": 15,
      "z": 0
    },
    {
      "x": -3,
      "y": 15,
      "z": 0
    },
    {
      "x": 3,
      "y": 5,
      "z": 0
    },
    {
      "x": -3,
      "y": 5,
      "z": 0
    }
  ],
  "VariantName": "KTO-18",
  "ChassisTags": {
    "items": [],
    "tagSetSourceFile": ""
  },
  "StockRole": "Juggernaut & Close Assault",
  "YangsThoughts": "The Kintaro 18 is… a beast of a machine. It's fast for its size, mounts heavy armor, and can literally shred most other 'Mechs with its almost ridiculous amount of close-range weaponry. But, it runs hotter than Hell's saunas."
}
```

As you can see it's not complete definition, as it refers to `movedef_kintaro_KTO-18`, and `hardpointdatadef_kintaro` for further details, but it's still just more JSONs.

### Example Weapon JSON

Here's an example of weapon JSON - `weapon/Weapon_SRM_SRM6_0-STOCK.json`.

It lacks one thing we want, number of times it can fire per 1ton ammo box, other than that it's all in one JSON:

```json
{
  "Category": "Missile",
  "Type": "SRM",
  "WeaponSubType": "SRM6",
  "MinRange": 0,
  "MaxRange": 270,
  "RangeSplit": [
    180,
    180,
    270
  ],
  "ammoCategoryID": "SRM",
  "StartingAmmoCapacity": 0,
  "HeatGenerated": 12,
  "Damage": 8,
  "OverheatedDamageMultiplier": 0,
  "EvasiveDamageMultiplier": 0,
  "EvasivePipsIgnored": 0,
  "DamageVariance": 0,
  "HeatDamage": 0,
  "AccuracyModifier": 0,
  "CriticalChanceMultiplier": 1,
  "AOECapable": false,
  "IndirectFireCapable": false,
  "RefireModifier": 0,
  "ShotsWhenFired": 6,
  "ProjectilesPerShot": 1,
  "AttackRecoil": 1,
  "Instability": 3,
  "WeaponEffectID": "WeaponEffect-Weapon_SRM6",
  "Description": {
    "Cost": 90000,
    "Rarity": 0,
    "Purchasable": true,
    "Manufacturer": "TharHes",
    "Model": "Short-Range Missile Launcher",
    "UIName": "SRM6",
    "Id": "Weapon_SRM_SRM6_0-STOCK",
    "Name": "SRM6",
    "Details": "SRM6s are the largest available rack of Short-Range Missiles and pack considerable punch while also being lightweight. As for all SRMs, SRM6 shots deal above-average stability impact per hit compared to other weapons.",
    "Icon": "uixSvgIcon_weapon_Missile"
  },
  "BonusValueA": "",
  "BonusValueB": "",
  "ComponentType": "Weapon",
  "ComponentSubType": "Weapon",
  "PrefabIdentifier": "srm6",
  "BattleValue": 0,
  "InventorySize": 2,
  "Tonnage": 3,
  "AllowedLocations": "All",
  "DisallowedLocations": "All",
  "CriticalComponent": false,
  "statusEffects": [],
  "ComponentTags": {
    "items": [
      "component_type_stock",
      "range_standard"
    ],
    "tagSetSourceFile": ""
  }
}
```

### Coming next

In the next few episodes, we'll write some command line tool to load the game data and output some analysis.

The first thing we'll want to explore is weapons' damage per ton rankings.