ModTek is a modding system for HBS's BATTLETECH PC that allows modders to package their mods in a self-contained manner without overwritting game files. ModTek is run at game startup and initializies other mods that conform to the mod.json format. In this way, it allows for the dynamic loading of mods at runtime with dependancies resolved and load order enforced, without having to edit the dreaded VersionManifest.csv
. It also provides for incrementatal patching of stock game files that are easy to remove, version, and persist through patches.
since 0.7.6.7 ModTek supports loading Wwise sound banks definitions example:
{
"name": "Screams",
"filename": "Screams.bnk",
"type": "Combat",
"volumeRTPCIds":[2081458675],
"volumeShift": 0,
"events":{
"scream01":147496415
}
}
name - is unique name of sound bank
filename - is name of file containing real audio content. Battletech is using Wwise 2016.2
type - type of sound bank.
Avaible types:
- Combat - banks of this type always loading at combat start and unloading at combat end. Should be used for sounds played on battlefield.
- Default - banks of this type loading at game start. Moistly used for UI
- Voice - banks of this type contains pilot's voices
events - map of events exported in bank. Needed for events can be referenced from code via WwiseManager.PostEvent which takes this name as parameter
volumeRTPCIds - list of RTPC ids controlling loudness of samples. Combat sound banks controlled by effect settings volume slider, Voice by voice
dynamic enums handled outside manifest array. By DataAddendumEntries name - name of type. Supporting types BattleTech.FactionEnumeration, BattleTech.WeaponCategoryEnumeration, BattleTech.AmmoCategoryEnumeration, BattleTech.ContractTypeEnumeration path - path to file relative to mod root folder. Examples for content at BattleTech_Data\StreamingAssets\data\enums\ .
example:
"DataAddendumEntries":[
{
"name": "BattleTech.FactionEnumeration",
"path": "Faction.json"
},
{
"name": "BattleTech.WeaponCategoryEnumeration",
"path": "WeaponCategory.json"
}
]
ModTek 0.7.6+ can be disabled or enabled with fancy build-in mods menu. If ModTek enabled "MODS ENABLED" check box will be always enabled. To disable ModTek you should find mod named "ModTek" in list and disable it. After restart game will use build-in mod loader. To enable ModTek you should find mod named "ModTek" in list and enable it. After restart game will use ModTek.
ModTek 0.6.* and forward integrates BTML so installing is even easier than it used to be.
- Download a release from here.
- If the
BATTLETECH\Mods\
directory doesn't exist, create it. - Move the entire ModTek folder from the release download into the
BATTLETECH\Mods\
folder - You should now have a
BATTLETECH\Mods\ModTek\
folder - Run the injector program (
ModTekInjector.exe
) in that folder - DO NOT move anything from the
BATTLETECH\Mods\ModTek\
folder, it is self-contained.
NOTE: BATTLETECH\Mods\
is in game installation folder NOT in Documents\My Games
On game startup, ModTek decorates the version number found in the bottom left corner of the main menu (introduced in Patch 1.01) with "/W MODTEK". If you don't see this and you're beyond patch 1.01, something has gone wrong.
Generally, updates can be dealt with by re-running ModTekInjector.exe
-- though sometimes ModTek will have to if certain game API's change. If the game has changed in a way that breaks an individual mod, that mod will need to be updated, but generally, this will only happen in large updates.
\BATTLETECH\Mods\
\ReallyCoolMod\
mod.json
LegomanCoolMod.dll
MyCustomWeaponDefsFolder\
Weapon_UAC5.json
Weapon_LB10_X.json
MyCustomMech\
mechdef_my_super_mech.json
StreamingAssets\data\weapon\
Weapon_Autocannon_AC5_0-Stock.json
It all begins with a mod.json
file in the root of your mods subdirectory. This is the only non-optional part of ModTek. Contained within is metadata that determines how your mod is loaded, what order it is loaded in, and an optional settings block for configuring your mod (which is only applicable for mods that include a DLL). Further documentation for the mod.json
format is here.
Here's an example mod.json
:
{
"Name": "ReallyCoolMod",
"Version": "0.0.1",
"Enabled": true,
"DependsOn": [ "ReallyCoolOtherMod", "RadicalMod" ],
"ConflictsWith": [ "IncompatableMod" ],
"DLL": "LegomanCoolMod.dll",
"DLLEntryPoint": "CoolModNamespace.CoolModClass.CoolModPublicStaticMethod",
"Manifest": [
{ "Type": "WeaponDef", "Path": "MyCustomWeaponDefsFolder\\" },
{ "Type": "MechDef", "Path": "MyCustomMech\\mechdef_my_super_mech.json" }
],
"Settings": {
"CoolSetting": 450,
"GreatSetting": true
}
}
The only required field is "Name" which must be unique between all installed mods in a session. The other fields are optional with some having default values, but it is highly recommended that you fill them in for mods intended for distribution. Many of those fields are self-explanatory -- but currently they are only read at game startup. Again, you can read about the mod.json
format in-depth here.
If a DLL is supplied with your mod, in order to be loaded and run, it will need to have a path and file name given. Optionally, you can specify an entry point, which defaults to calling all public static Init(void)
on all classes in your assembly. Some parameters are supported coming into your entry point.
The "Manifest" entry here is of particular note, as this will load files into the VersionManifest
at load. By default, ModTek assumes that files in \MyModDirectory\StreamingAssets\
are mirrors of base game files in contained in \BattleTech_Data\StreamingAssets\
and will load those files without needing to be told about them.
For JSON files of specific types, if a file is loaded that has the same ID as a file that is already in the game, instead of completely replacing the file, ModTek will do a simple merge of the two types when these files are deserialized from JSON. Here's a simple example of a mod that changes the AC/5s damage to give it a little boost.
mod.json
:
{
"Name": "BoostedAC5",
"Version": "0.0.1"
}
\BoostedAC5\StreamingAssets\data\weapon\Weapon_Autocannon_AC5_0-Stock.json
:
{
"Damage": 450
}
This change would make the AC/5 do 450 damage and all other settings on the stock AC/5 will be unchanged.
Note: Because of the way ModTek loads mods, the last mod to change a property "wins". Because of this, you should probably not copy the entire file from the stock folder into your mod and make a few changes. Only include the values which you actually want to change.
Using the standard merging mechanisms, one can't merge arrays or remove data. Advanced JSON Merging adds several ways to surgically manipulate existing JSONs using JSONPath.
To use Advanced JSON Merging, add a manifest to your mod.json
and add an "AdvancedJSONMerge" type entry to it. All jsons found under Path will be assumed to be AdvancedJSONMerge instruction files.
Example Manifest with Advanced JSON Merging entry.
"Manifest": [
{
"Type": "AdvancedJSONMerge",
"Path": "advanced"
}
]
Example Advanced JSON Merging instructions json advanced/blackknight_changes.json
Removes all heat sinks from the mech and then adds in back two heat sinks in the center torso.
{
"TargetID": "mechdef_blackknight_BL-6-KNT",
"Instructions": [
{
"JSONPath": "inventory[?(@.ComponentDefID == 'Gear_HeatSink_Generic_Standard')]",
"Action": "Remove"
},
{
"JSONPath": "inventory",
"Action": "ArrayConcat",
"Value": [
{
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
},
{
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
}
]
}
]
}
JSONPath is a good documented standard for navigating jsons. One can also find lots of solutions to problems on stackoverflow.
The sources of ModTek contain unit tests with some examples on how to use Actions.
ArrayAdd
adds a given value to the end of the target array.
{
"JSONPath": "inventory",
"Action": "ArrayAdd",
"Value": {
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
}
}
ArrayAddAfter
adds a given value after the target element in the array.
{
"JSONPath": "inventory[0]",
"Action": "ArrayAddAfter",
"Value": {
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
}
}
ArrayAddBefore
adds a given value before the target element in the array.
inventory[-1:]
references the last element of the inventory array.
Example adds a component to the second last position of the inventory.
{
"JSONPath": "inventory[-1:]",
"Action": "ArrayAddBefore",
"Value": {
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
}
}
ArrayConcat
adds a given array to the end of the target array.
Allows to add multiple elements quickly without having to "ArrayAdd" them individually.
{
"JSONPath": "inventory",
"Action": "ArrayConcat",
"Value": [
{
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
},
{
"MountedLocation": "CenterTorso",
"ComponentDefID": "Gear_HeatSink_Generic_Standard",
"ComponentDefType": "Upgrade",
"DamageLevel": "Functional"
}
]
}
ObjectMerge
merges a given object with the target objects.
Example selects the head location and sets new armor values.
{
"JSONPath": "Locations[?(@.Location == 'Head')]",
"Action": "ObjectMerge",
"Value": {
"CurrentArmor": 100,
"AssignedArmor": 100
}
}
Remove
removes the target element(s).
Example removes all components from inventory that are heat sinks.
{
"JSONPath": "inventory[?(@.ComponentDefID == 'Gear_HeatSink_Generic_Standard')]",
"Action": "Remove"
}
Replace
replaces the target with a given value.
Example replaces the mech tags with a new list of tags.
{
"JSONPath": "MechTags/items",
"Action": "Replace",
"Value": [
"unit_mech",
"unit_heavy",
"unit_role_brawler"
]
}
After checking out the project, you must update a configuration file with the full path to your BattleTech Game directory. Copy the file from CHANGEME.Directory.Build.props
to Directory.Build.props
. Open Directory.Build.props
in the editor of your choice, and replace CHANGEME_TO_FULL_PATH_TO_BTG_DIR
with the full path to your BattleTech Game (BTG) directory (one example - E:\steam\SteamApps\common\BATTLETECH
). Close and save. Directory.Build.props
is excluded in .gitignore
so you changes will not affect other developers, only you.
ℹ️ Linux users should note that Directory.Build.props
is case-sensitive. If you find the project won't compile for you, make sure the case is correct.
Once you can updated the configuration, open the VS solution and restore NuGet dependencies. You should be able to build ModTek once these steps are complete.
ModTek is provided under the Unlicense, which releases the work into the public domain.