Skip to content

Instantly share code, notes, and snippets.

@beccasaurus
Last active April 5, 2023 18:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beccasaurus/d9bfab84064b5ab76d15c0f4dae0e4a1 to your computer and use it in GitHub Desktop.
Save beccasaurus/d9bfab84064b5ab76d15c0f4dae0e4a1 to your computer and use it in GitHub Desktop.
Reading Mech data from MegaMek

Reading Mech data from MegaMek

MegaMek is licensed under the GNU General Public License v2.0

Therefore, this code is also licensed under GPLv2

The BattleTech IP is owned by The Topps Company, Inc and is licensed by Catalyst Game Labs.
The main website for BattleTech is here - BattleTech: The Board Game of Armored Combat

The MegaMek/megamek repository contains thousands of .mtf files containing definitions for BattleTech mechs and their information, e.g. this file defining the Marauder 3R

Version:1.0
Marauder
MAD-3R

Config:Biped
TechBase:Inner Sphere
Era:2819
Source:TRO 3039 - Succession Wars
Rules Level:1

Mass:75
Engine:300 Fusion Engine
Structure:Standard
Myomer:Standard

Heat Sinks:16 Single
Walk MP:4
Jump MP:0
...

The MegaMek program has logic to calculate the "Battle Value" of mechs which is composed of lots of individual values:

 /**
     * Calculates the battle value of this mech. If the parameter is true, then
     * the battle value for c3 won't be added whether the mech is currently part
     * of a network or not.
     */
    @Override
    public int calculateBattleValue(boolean ignoreC3, boolean ignorePilot) {
      // ...
    }

This simply script uses the (GPLv2 licensed) megamek.common library to:

  1. Parse .mtf files into Mech objects (using megamek.common.MechFileParser)
  2. Print out a few interesting values about Mechs' Battle Value

    Tweaked the Mech.java file to print out the values we were interested in

Running the Code

Tested on Ubuntu 20.04 using openjdk-11-jdk (sudo apt install openjdk-11-jdk) and JRuby 9.2.11.1

To allow for this to be a simple small script, JRuby was used to load and call the megamek.common library.

Note: I ran into this error: java.lang.NoClassDefFoundError: javax/xml/bind/PropertyException
This is because the JAXB APIs were removed from JDK 11, more info on StackOverflow.

To resolve this, apply the provided diff to build.gradle which adds the jakarta.xml.bind dependency.

  1. Clone the latest version of MegaMek/megamek

    SHA ab24f5dcb0e3d7aab9394ae9449b5adcc312677c at time of writing

    git clone --depth 1 https://github.com/MegaMek/megamek.git
  2. cd megamek
  3. Edit megamek/src/megamek/common/Mech.java to output whatever BV info you're interested in

    e.g. Apply the provided diff to Mech.java which prints the:

    • Total BV of all Defensive Equipment
    • Defensive Movement Factor
    • Total Weapons BV Adjusted For Heat
    • Total BV
  4. ./gradlew build
  5. Run jruby print_mech_info.rb which prints these 3 values for all mechs

    The script specifically prints info for all .mtf files in the megamek/data/mechfiles/mechs folder

That's it!

Thank you MegaMek for doing this complex calculation and providing mech info.

MegaMek and this Gist code are licensed under the GPLv2

Interactive Mech API

If you want to load up a Mech and call its methods:

  1. Choose an .mtf file (and note its path, e.g. megamek/data/mechfiles/mechs/3039u/Archer ARC-2R.mtf)
  2. Boot up an interactive JRuby session via jirb
  3. Paste in the following code:
    mtf_file_path = "megamek/data/mechfiles/mechs/3039u/Archer ARC-2R.mtf" # <-- path to .mtf
    
    $LOAD_PATH.push "megamek/build/launch4j/lib/"
    $LOAD_PATH.unshift "megamek/build/libs/"
    require "java"
    require "MegaMek.jar"
    require "jakarta.activation-api-1.2.1.jar"
    require "jakarta.xml.bind-api-2.3.2.jar"
    java_import "megamek.common.MechFileParser"
    
    mtf_file = Java::JavaIo::File.new mtf_file_path
    parser = Java::MegamekCommon::MechFileParser.new mtf_file
    mech = parser.get_entity
  4. Now you have a Mech object, e.g. Java::MegamekCommon::BipedMech.
    Try calling a method:
    irb(main):014:0> mech.display_name
    => "Archer ARC-2R"
    irb(main):020:0> mech.weapons.map(&:name)
    => ["Medium Laser", "Medium Laser", "LRM 20", "LRM 20", "Medium Laser", "Medium Laser"]
  5. You're good to go! Read the Mech API and poke around.
diff --git a/build.gradle b/build.gradle
index 75e91514..1009d1a9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,3 +37,15 @@ if (localProperties.exists()) {
apply from: localProperties
}
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3'
+}
Copyright (C) 2021 Rebecca Taylor
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
diff --git a/megamek/src/megamek/common/Mech.java b/megamek/src/megamek/common/Mech.java
index efcb2f29..cd094cb6 100644
--- a/megamek/src/megamek/common/Mech.java
+++ b/megamek/src/megamek/common/Mech.java
@@ -20,6 +20,7 @@ import java.io.PrintWriter;
import java.math.BigInteger;
import java.text.NumberFormat;
import java.util.*;
+import java.io.*;
import megamek.MegaMek;
import megamek.common.loaders.MtfFile;
@@ -3380,9 +3381,9 @@ public abstract class Mech extends Entity {
*/
@Override
public int calculateBattleValue() {
- if (useManualBV) {
- return manualBV;
- }
+ // if (useManualBV) {
+ // return manualBV;
+ // }
return calculateBattleValue(false, false);
}
@@ -3393,9 +3394,9 @@ public abstract class Mech extends Entity {
*/
@Override
public int calculateBattleValue(boolean ignoreC3, boolean ignorePilot) {
- if (useManualBV) {
- return manualBV;
- }
+ // if (useManualBV) {
+ // return manualBV;
+ // }
bvText = new StringBuffer(
"<HTML><BODY><CENTER><b>Battle Value Calculations For ");
@@ -3863,6 +3864,7 @@ public abstract class Mech extends Entity {
bvText.append(endColumn);
bvText.append(startColumn);
bvText.append(dbv);
+ System.out.print("" + dbv + "\t");
bvText.append(endColumn);
bvText.append(endRow);
@@ -4049,6 +4051,7 @@ public abstract class Mech extends Entity {
bvText.append(startColumn);
bvText.append(" x ");
bvText.append(tmmFactor);
+ System.out.print("" + tmmFactor + "\t");
bvText.append(endColumn);
bvText.append(endRow);
@@ -4867,6 +4870,7 @@ public abstract class Mech extends Entity {
bvText.append(startColumn);
bvText.append(endColumn);
bvText.append(startColumn);
+ System.out.print("" + weaponBV + "\t");
bvText.append(weaponBV);
bvText.append(endColumn);
bvText.append(endRow);
@@ -5307,6 +5311,8 @@ public abstract class Mech extends Entity {
int retVal = (int) Math.round((finalBV) * pilotFactor);
+ System.out.println(retVal);
+
return retVal;
}
@@ -5584,6 +5590,9 @@ public abstract class Mech extends Entity {
bvText.append(endTable);
bvText.append("</BODY></HTML>");
+
+ // System.out.println(bvText.toString());
+
/*
* maxLeft += 5; // leave some padding in the middle maxRight =
* Math.max(maxRight, commafy.format(cost).length()); for (int i = 0; i
# sudo apt install openjdk-11-jdk
MTF_FILES = Dir["megamek/data/mechfiles/mechs/**/*.mtf"]
$LOAD_PATH.push "megamek/build/launch4j/lib/"
$LOAD_PATH.unshift "megamek/build/libs/"
require "java"
require "MegaMek.jar"
require "jakarta.activation-api-1.2.1.jar"
require "jakarta.xml.bind-api-2.3.2.jar"
java_import "megamek.common.MechFileParser"
puts
puts [
"Name",
"Model",
"Tonage",
"Weight Class",
"Tech Base",
"Rules Level",
"Any ECM",
"Any C3",
"C3",
"C3i",
"C3 s",
"Boosted C3",
"Defensive BV",
"Defensive Movement Factor",
"Weapons BV Adjusted for Heat",
"Battle Value" ].join "\t"
MTF_FILES.each do |mtf_file_path|
mtf_file = Java::JavaIo::File.new mtf_file_path
parser = Java::MegamekCommon::MechFileParser.new mtf_file
mech = parser.get_entity
print [
mech.display_name.sub(mech.model, '').strip,
mech.model,
mech.weight,
mech.weight_class_name,
(mech.tech_base == 2 ? "Clan" : "IS"),
File.read(mtf_file_path).scan(/rules level:(\d+)/i).flatten[0],
((mech.ecm_strength && mech.ecm_strength > 0.0) ? 'TRUE' : 'FALSE'),
mech.has_any_c3_system?,
mech.has_c3?,
mech.has_c3i?,
mech.has_c3_s?,
mech.has_boosted_c3?
].join "\t"
print "\t"
mech.calculate_battle_value
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment